redis-benchmarks-specification 0.1.70__py3-none-any.whl → 0.1.73__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.

Potentially problematic release.


This version of redis-benchmarks-specification might be problematic. Click here for more details.

Files changed (55) hide show
  1. redis_benchmarks_specification/__builder__/builder.py +2 -0
  2. redis_benchmarks_specification/__cli__/args.py +6 -0
  3. redis_benchmarks_specification/__cli__/cli.py +5 -1
  4. redis_benchmarks_specification/__compare__/__init__.py +5 -0
  5. redis_benchmarks_specification/__compare__/args.py +139 -0
  6. redis_benchmarks_specification/__compare__/compare.py +1153 -0
  7. redis_benchmarks_specification/__runner__/runner.py +120 -59
  8. redis_benchmarks_specification/__self_contained_coordinator__/args.py +3 -0
  9. redis_benchmarks_specification/__self_contained_coordinator__/build_info.py +7 -0
  10. redis_benchmarks_specification/__self_contained_coordinator__/runners.py +1 -0
  11. redis_benchmarks_specification/__self_contained_coordinator__/self_contained_coordinator.py +472 -418
  12. redis_benchmarks_specification/test-suites/create-re-string.py +286 -0
  13. redis_benchmarks_specification/test-suites/generate.py +108 -0
  14. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hexists.yml +1 -1
  15. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hincrby.yml +2 -2
  16. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-200KiB-values.yml +37 -0
  17. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-20KiB-values.yml +37 -0
  18. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-2MB-values.yml +37 -0
  19. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-decr.yml +33 -0
  20. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-200KiB.yml +33 -0
  21. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-20KiB.yml +33 -0
  22. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-2MB.yml +33 -0
  23. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements-long-scores.yml +2 -2
  24. redis_benchmarks_specification/test-suites/my-new-test.yml +16 -0
  25. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-load-string200c-with-20KiB-values-pipeline-10.yml +20 -0
  26. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-load-string200c-with-20KiB-values.yml +20 -0
  27. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-load-string50c-with-20KiB-values-pipeline-10.yml +20 -0
  28. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-load-string50c-with-20KiB-values.yml +20 -0
  29. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-string-setget200c-20KiB-pipeline-10.yml +26 -0
  30. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-string-setget200c-20KiB.yml +26 -0
  31. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-string-setget50c-20KiB-pipeline-10.yml +26 -0
  32. redis_benchmarks_specification/test-suites/string/memtier_benchmark-100Kkeys-string-setget50c-20KiB.yml +26 -0
  33. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string200c-with-100B-values-pipeline-10.yml +20 -0
  34. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string200c-with-100B-values.yml +20 -0
  35. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string200c-with-1KiB-values-pipeline-10.yml +20 -0
  36. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string200c-with-1KiB-values.yml +20 -0
  37. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string50c-with-100B-values-pipeline-10.yml +20 -0
  38. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string50c-with-100B-values.yml +20 -0
  39. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string50c-with-1KiB-values-pipeline-10.yml +20 -0
  40. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-load-string50c-with-1KiB-values.yml +20 -0
  41. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-mget-1KiB.yml +27 -0
  42. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget200c-100B-pipeline-10.yml +26 -0
  43. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget200c-100B.yml +26 -0
  44. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-10.yml +26 -0
  45. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget200c-1KiB.yml +26 -0
  46. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget50c-100B-pipeline-10.yml +26 -0
  47. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget50c-100B.yml +26 -0
  48. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget50c-1KiB-pipeline-10.yml +26 -0
  49. redis_benchmarks_specification/test-suites/string/memtier_benchmark-1Mkeys-string-setget50c-1KiB.yml +26 -0
  50. redis_benchmarks_specification/test-suites/template.txt +16 -0
  51. {redis_benchmarks_specification-0.1.70.dist-info → redis_benchmarks_specification-0.1.73.dist-info}/METADATA +3 -4
  52. {redis_benchmarks_specification-0.1.70.dist-info → redis_benchmarks_specification-0.1.73.dist-info}/RECORD +55 -16
  53. {redis_benchmarks_specification-0.1.70.dist-info → redis_benchmarks_specification-0.1.73.dist-info}/WHEEL +1 -1
  54. {redis_benchmarks_specification-0.1.70.dist-info → redis_benchmarks_specification-0.1.73.dist-info}/entry_points.txt +1 -0
  55. {redis_benchmarks_specification-0.1.70.dist-info → redis_benchmarks_specification-0.1.73.dist-info}/LICENSE +0 -0
@@ -0,0 +1,1153 @@
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
+ from github import Github
18
+ from slack_sdk.webhook import WebhookClient
19
+ import argparse
20
+ from redis_benchmarks_specification.__compare__.args import create_compare_arguments
21
+
22
+
23
+ from redis_benchmarks_specification.__common__.package import (
24
+ get_version_string,
25
+ populate_with_poetry_data,
26
+ )
27
+
28
+
29
+ WH_TOKEN = os.getenv("PERFORMANCE_WH_TOKEN", None)
30
+
31
+ LOG_LEVEL = logging.DEBUG
32
+ if os.getenv("VERBOSE", "0") == "0":
33
+ LOG_LEVEL = logging.INFO
34
+ LOG_FORMAT = "%(asctime)s %(levelname)-4s %(message)s"
35
+ LOG_DATEFMT = "%Y-%m-%d %H:%M:%S"
36
+
37
+
38
+ def get_overall_dashboard_keynames(
39
+ tf_github_org,
40
+ tf_github_repo,
41
+ tf_triggering_env,
42
+ build_variant_name=None,
43
+ running_platform=None,
44
+ test_name=None,
45
+ ):
46
+ build_variant_str = ""
47
+ if build_variant_name is not None:
48
+ build_variant_str = "/{}".format(build_variant_name)
49
+ running_platform_str = ""
50
+ if running_platform is not None:
51
+ running_platform_str = "/{}".format(running_platform)
52
+ sprefix = (
53
+ "ci.benchmarks.redislabs/"
54
+ + "{triggering_env}/{github_org}/{github_repo}".format(
55
+ triggering_env=tf_triggering_env,
56
+ github_org=tf_github_org,
57
+ github_repo=tf_github_repo,
58
+ )
59
+ )
60
+ testcases_setname = "{}:testcases".format(sprefix)
61
+ deployment_name_setname = "{}:deployment_names".format(sprefix)
62
+ project_archs_setname = "{}:archs".format(sprefix)
63
+ project_oss_setname = "{}:oss".format(sprefix)
64
+ project_branches_setname = "{}:branches".format(sprefix)
65
+ project_versions_setname = "{}:versions".format(sprefix)
66
+ project_compilers_setname = "{}:compilers".format(sprefix)
67
+ running_platforms_setname = "{}:platforms".format(sprefix)
68
+ build_variant_setname = "{}:build_variants".format(sprefix)
69
+ build_variant_prefix = "{sprefix}{build_variant_str}".format(
70
+ sprefix=sprefix,
71
+ build_variant_str=build_variant_str,
72
+ )
73
+ prefix = "{build_variant_prefix}{running_platform_str}".format(
74
+ build_variant_prefix=build_variant_prefix,
75
+ running_platform_str=running_platform_str,
76
+ )
77
+ tsname_project_total_success = "{}:total_success".format(
78
+ prefix,
79
+ )
80
+ tsname_project_total_failures = "{}:total_failures".format(
81
+ prefix,
82
+ )
83
+ testcases_metric_context_path_setname = ""
84
+ if test_name is not None:
85
+ testcases_metric_context_path_setname = (
86
+ "{testcases_setname}:metric_context_path:{test_name}".format(
87
+ testcases_setname=testcases_setname, test_name=test_name
88
+ )
89
+ )
90
+ testcases_and_metric_context_path_setname = (
91
+ "{testcases_setname}_AND_metric_context_path".format(
92
+ testcases_setname=testcases_setname
93
+ )
94
+ )
95
+ return (
96
+ prefix,
97
+ testcases_setname,
98
+ deployment_name_setname,
99
+ tsname_project_total_failures,
100
+ tsname_project_total_success,
101
+ running_platforms_setname,
102
+ build_variant_setname,
103
+ testcases_metric_context_path_setname,
104
+ testcases_and_metric_context_path_setname,
105
+ project_archs_setname,
106
+ project_oss_setname,
107
+ project_branches_setname,
108
+ project_versions_setname,
109
+ project_compilers_setname,
110
+ )
111
+
112
+
113
+ def get_start_time_vars(start_time=None):
114
+ if start_time is None:
115
+ start_time = dt.datetime.utcnow()
116
+ start_time_ms = int((start_time - dt.datetime(1970, 1, 1)).total_seconds() * 1000)
117
+ start_time_str = start_time.strftime("%Y-%m-%d-%H-%M-%S")
118
+ return start_time, start_time_ms, start_time_str
119
+
120
+
121
+ def get_project_compare_zsets(triggering_env, org, repo):
122
+ return "ci.benchmarks.redislabs/{}/{}/{}:compare:pull_requests:zset".format(
123
+ triggering_env, org, repo
124
+ )
125
+
126
+
127
+ def compare_command_logic(args, project_name, project_version):
128
+
129
+ logger = logging.getLogger()
130
+ logger.setLevel(LOG_LEVEL)
131
+
132
+ # create console handler and set level to debug
133
+ ch = logging.StreamHandler()
134
+ ch.setLevel(LOG_LEVEL)
135
+
136
+ # create formatter
137
+ formatter = logging.Formatter(LOG_FORMAT)
138
+
139
+ # add formatter to ch
140
+ ch.setFormatter(formatter)
141
+
142
+ # add ch to logger
143
+ logger.addHandler(ch)
144
+
145
+ logging.info(
146
+ "Using: {project_name} {project_version}".format(
147
+ project_name=project_name, project_version=project_version
148
+ )
149
+ )
150
+ logging.info(
151
+ "Checking connection to RedisTimeSeries with user: {}, host: {}, port: {}".format(
152
+ args.redistimeseries_user,
153
+ args.redistimeseries_host,
154
+ args.redistimeseries_port,
155
+ )
156
+ )
157
+ rts = redis.Redis(
158
+ host=args.redistimeseries_host,
159
+ port=args.redistimeseries_port,
160
+ password=args.redistimeseries_pass,
161
+ username=args.redistimeseries_user,
162
+ )
163
+ rts.ping()
164
+ default_baseline_branch = None
165
+ default_metrics_str = ""
166
+ if args.defaults_filename != "" and os.path.exists(args.defaults_filename):
167
+ logging.info(
168
+ "Loading configuration from defaults file: {}".format(
169
+ args.defaults_filename
170
+ )
171
+ )
172
+ with open(args.defaults_filename) as yaml_fd:
173
+ defaults_dict = yaml.safe_load(yaml_fd)
174
+ if "exporter" in defaults_dict:
175
+ exporter_dict = defaults_dict["exporter"]
176
+ if "comparison" in exporter_dict:
177
+ comparison_dict = exporter_dict["comparison"]
178
+ if "metrics" in comparison_dict:
179
+ metrics = comparison_dict["metrics"]
180
+ logging.info("Detected defaults metrics info. reading metrics")
181
+ default_metrics = []
182
+
183
+ for metric in metrics:
184
+ if metric.startswith("$."):
185
+ metric = metric[2:]
186
+ logging.info("Will use metric: {}".format(metric))
187
+ default_metrics.append(metric)
188
+ if len(default_metrics) == 1:
189
+ default_metrics_str = default_metrics[0]
190
+ if len(default_metrics) > 1:
191
+ default_metrics_str = "({})".format(
192
+ ",".join(default_metrics)
193
+ )
194
+ logging.info("Default metrics: {}".format(default_metrics_str))
195
+
196
+ if "baseline-branch" in comparison_dict:
197
+ default_baseline_branch = comparison_dict["baseline-branch"]
198
+ logging.info(
199
+ "Detected baseline branch in defaults file. {}".format(
200
+ default_baseline_branch
201
+ )
202
+ )
203
+
204
+ tf_github_org = args.github_org
205
+ tf_github_repo = args.github_repo
206
+ tf_triggering_env = args.triggering_env
207
+ if args.baseline_deployment_name != "":
208
+ baseline_deployment_name = args.baseline_deployment_name
209
+ else:
210
+ baseline_deployment_name = args.deployment_name
211
+ if args.comparison_deployment_name != "":
212
+ comparison_deployment_name = args.comparison_deployment_name
213
+ else:
214
+ comparison_deployment_name = args.deployment_name
215
+
216
+ logging.info(
217
+ "Using baseline deployment_name={} and comparison deployment_name={} for the analysis".format(
218
+ baseline_deployment_name,
219
+ comparison_deployment_name,
220
+ )
221
+ )
222
+ from_ts_ms = args.from_timestamp
223
+ to_ts_ms = args.to_timestamp
224
+ from_date = args.from_date
225
+ to_date = args.to_date
226
+ baseline_branch = args.baseline_branch
227
+ if baseline_branch is None and default_baseline_branch is not None:
228
+ logging.info(
229
+ "Given --baseline-branch was null using the default baseline branch {}".format(
230
+ default_baseline_branch
231
+ )
232
+ )
233
+ baseline_branch = default_baseline_branch
234
+ comparison_branch = args.comparison_branch
235
+ simplify_table = args.simple_table
236
+ print_regressions_only = args.print_regressions_only
237
+ print_improvements_only = args.print_improvements_only
238
+ skip_unstable = args.skip_unstable
239
+ baseline_tag = args.baseline_tag
240
+ comparison_tag = args.comparison_tag
241
+ last_n_baseline = args.last_n
242
+ last_n_comparison = args.last_n
243
+ if last_n_baseline < 0:
244
+ last_n_baseline = args.last_n_baseline
245
+ if last_n_comparison < 0:
246
+ last_n_comparison = args.last_n_comparison
247
+ logging.info("Using last {} samples for baseline analysis".format(last_n_baseline))
248
+ logging.info(
249
+ "Using last {} samples for comparison analysis".format(last_n_comparison)
250
+ )
251
+ verbose = args.verbose
252
+ regressions_percent_lower_limit = args.regressions_percent_lower_limit
253
+ metric_name = args.metric_name
254
+ if (metric_name is None or metric_name == "") and default_metrics_str != "":
255
+ logging.info(
256
+ "Given --metric_name was null using the default metric names {}".format(
257
+ default_metrics_str
258
+ )
259
+ )
260
+ metric_name = default_metrics_str
261
+
262
+ if metric_name is None:
263
+ logging.error(
264
+ "You need to provider either "
265
+ + " --metric_name or provide a defaults file via --defaults_filename that contains exporter.redistimeseries.comparison.metrics array. Exiting..."
266
+ )
267
+ exit(1)
268
+ else:
269
+ logging.info("Using metric {}".format(metric_name))
270
+
271
+ metric_mode = args.metric_mode
272
+ test = args.test
273
+ use_metric_context_path = args.use_metric_context_path
274
+ github_token = args.github_token
275
+ pull_request = args.pull_request
276
+ testname_regex = args.testname_regex
277
+ auto_approve = args.auto_approve
278
+ running_platform = args.running_platform
279
+ grafana_base_dashboard = args.grafana_base_dashboard
280
+ # using an access token
281
+ is_actionable_pr = False
282
+ contains_regression_comment = False
283
+ regression_comment = None
284
+ github_pr = None
285
+ # slack related
286
+ webhook_notifications_active = False
287
+ webhook_client_slack = None
288
+ if running_platform is not None:
289
+ logging.info(
290
+ "Using platform named: {} to do the comparison.\n\n".format(
291
+ running_platform
292
+ )
293
+ )
294
+
295
+ old_regression_comment_body = ""
296
+ if github_token is not None:
297
+ logging.info("Detected github token")
298
+ g = Github(github_token)
299
+ if pull_request is not None and pull_request != "":
300
+ pull_request_n = int(pull_request)
301
+ github_pr = (
302
+ g.get_user(tf_github_org)
303
+ .get_repo(tf_github_repo)
304
+ .get_issue(pull_request_n)
305
+ )
306
+ comments = github_pr.get_comments()
307
+ pr_link = github_pr.html_url
308
+ logging.info("Working on github PR already: {}".format(pr_link))
309
+ is_actionable_pr = True
310
+ contains_regression_comment, pos = check_regression_comment(comments)
311
+ if contains_regression_comment:
312
+ regression_comment = comments[pos]
313
+ old_regression_comment_body = regression_comment.body
314
+ logging.info(
315
+ "Already contains regression comment. Link: {}".format(
316
+ regression_comment.html_url
317
+ )
318
+ )
319
+ if verbose:
320
+ logging.info("Printing old regression comment:")
321
+ print("".join(["-" for x in range(1, 80)]))
322
+ print(regression_comment.body)
323
+ print("".join(["-" for x in range(1, 80)]))
324
+ else:
325
+ logging.info("Does not contain regression comment")
326
+
327
+ grafana_dashboards_uids = {
328
+ "redisgraph": "SH9_rQYGz",
329
+ "redisbloom": "q4-5sRR7k",
330
+ "redisearch": "3Ejv2wZnk",
331
+ "redisjson": "UErSC0jGk",
332
+ "redistimeseries": "2WMw61UGz",
333
+ }
334
+ uid = None
335
+ if tf_github_repo.lower() in grafana_dashboards_uids:
336
+ uid = grafana_dashboards_uids[tf_github_repo.lower()]
337
+ grafana_link_base = None
338
+ if uid is not None:
339
+ grafana_link_base = "{}/{}".format(grafana_base_dashboard, uid)
340
+ logging.info(
341
+ "There is a grafana dashboard for this repo. Base link: {}".format(
342
+ grafana_link_base
343
+ )
344
+ )
345
+
346
+ (
347
+ detected_regressions,
348
+ table_output,
349
+ total_improvements,
350
+ total_regressions,
351
+ total_stable,
352
+ total_unstable,
353
+ total_comparison_points,
354
+ ) = compute_regression_table(
355
+ rts,
356
+ tf_github_org,
357
+ tf_github_repo,
358
+ tf_triggering_env,
359
+ metric_name,
360
+ comparison_branch,
361
+ baseline_branch,
362
+ baseline_tag,
363
+ comparison_tag,
364
+ baseline_deployment_name,
365
+ comparison_deployment_name,
366
+ print_improvements_only,
367
+ print_regressions_only,
368
+ skip_unstable,
369
+ regressions_percent_lower_limit,
370
+ simplify_table,
371
+ test,
372
+ testname_regex,
373
+ verbose,
374
+ last_n_baseline,
375
+ last_n_comparison,
376
+ metric_mode,
377
+ from_date,
378
+ from_ts_ms,
379
+ to_date,
380
+ to_ts_ms,
381
+ use_metric_context_path,
382
+ running_platform,
383
+ )
384
+ comment_body = ""
385
+ if total_comparison_points > 0:
386
+ comment_body = "### Automated performance analysis summary\n\n"
387
+ comment_body += "This comment was automatically generated given there is performance data available.\n\n"
388
+ if running_platform is not None:
389
+ comment_body += "Using platform named: {} to do the comparison.\n\n".format(
390
+ running_platform
391
+ )
392
+ comparison_summary = "In summary:\n"
393
+ if total_stable > 0:
394
+ comparison_summary += (
395
+ "- Detected a total of {} stable tests between versions.\n".format(
396
+ total_stable,
397
+ )
398
+ )
399
+
400
+ if total_unstable > 0:
401
+ comparison_summary += (
402
+ "- Detected a total of {} highly unstable benchmarks.\n".format(
403
+ total_unstable
404
+ )
405
+ )
406
+ if total_improvements > 0:
407
+ comparison_summary += "- Detected a total of {} improvements above the improvement water line.\n".format(
408
+ total_improvements
409
+ )
410
+ if total_regressions > 0:
411
+ comparison_summary += "- Detected a total of {} regressions bellow the regression water line {}.\n".format(
412
+ total_regressions, args.regressions_percent_lower_limit
413
+ )
414
+
415
+ comment_body += comparison_summary
416
+ comment_body += "\n"
417
+
418
+ if grafana_link_base is not None:
419
+ grafana_link = "{}/".format(grafana_link_base)
420
+ if baseline_tag is not None and comparison_tag is not None:
421
+ grafana_link += "?var-version={}&var-version={}".format(
422
+ baseline_tag, comparison_tag
423
+ )
424
+ if baseline_branch is not None and comparison_branch is not None:
425
+ grafana_link += "?var-branch={}&var-branch={}".format(
426
+ baseline_branch, comparison_branch
427
+ )
428
+ comment_body += "You can check a comparison in detail via the [grafana link]({})".format(
429
+ grafana_link
430
+ )
431
+
432
+ comment_body += "\n\n##" + table_output
433
+ print(comment_body)
434
+
435
+ if is_actionable_pr:
436
+ zset_project_pull_request = get_project_compare_zsets(
437
+ tf_triggering_env,
438
+ tf_github_org,
439
+ tf_github_repo,
440
+ )
441
+ logging.info(
442
+ "Populating the pull request performance ZSETs: {} with branch {}".format(
443
+ zset_project_pull_request, comparison_branch
444
+ )
445
+ )
446
+ _, start_time_ms, _ = get_start_time_vars()
447
+ res = rts.zadd(
448
+ zset_project_pull_request,
449
+ {comparison_branch: start_time_ms},
450
+ )
451
+ logging.info(
452
+ "Result of Populating the pull request performance ZSETs: {} with branch {}: {}".format(
453
+ zset_project_pull_request, comparison_branch, res
454
+ )
455
+ )
456
+ user_input = "n"
457
+ html_url = "n/a"
458
+ regression_count = len(detected_regressions)
459
+ (
460
+ baseline_str,
461
+ by_str_baseline,
462
+ comparison_str,
463
+ by_str_comparison,
464
+ ) = get_by_strings(
465
+ baseline_branch,
466
+ comparison_branch,
467
+ baseline_tag,
468
+ comparison_tag,
469
+ )
470
+
471
+ if contains_regression_comment:
472
+ same_comment = False
473
+ if comment_body == old_regression_comment_body:
474
+ logging.info(
475
+ "The old regression comment is the same as the new comment. skipping..."
476
+ )
477
+ same_comment = True
478
+ else:
479
+ logging.info(
480
+ "The old regression comment is different from the new comment. updating it..."
481
+ )
482
+ comment_body_arr = comment_body.split("\n")
483
+ old_regression_comment_body_arr = old_regression_comment_body.split(
484
+ "\n"
485
+ )
486
+ if verbose:
487
+ DF = [
488
+ x
489
+ for x in comment_body_arr
490
+ if x not in old_regression_comment_body_arr
491
+ ]
492
+ print("---------------------")
493
+ print(DF)
494
+ print("---------------------")
495
+ if same_comment is False:
496
+ if auto_approve:
497
+ print("auto approving...")
498
+ else:
499
+ user_input = input(
500
+ "Do you wish to update the comment {} (y/n): ".format(
501
+ regression_comment.html_url
502
+ )
503
+ )
504
+ if user_input.lower() == "y" or auto_approve:
505
+ print("Updating comment {}".format(regression_comment.html_url))
506
+ regression_comment.edit(comment_body)
507
+ html_url = regression_comment.html_url
508
+ print(
509
+ "Updated comment. Access it via {}".format(
510
+ regression_comment.html_url
511
+ )
512
+ )
513
+
514
+ else:
515
+ if auto_approve:
516
+ print("auto approving...")
517
+ else:
518
+ user_input = input(
519
+ "Do you wish to add a comment in {} (y/n): ".format(pr_link)
520
+ )
521
+ if user_input.lower() == "y" or auto_approve:
522
+ print("creating an comment in PR {}".format(pr_link))
523
+ regression_comment = github_pr.create_comment(comment_body)
524
+ html_url = regression_comment.html_url
525
+ print("created comment. Access it via {}".format(html_url))
526
+
527
+ else:
528
+ logging.error("There was no comparison points to produce a table...")
529
+ return (
530
+ detected_regressions,
531
+ comment_body,
532
+ total_improvements,
533
+ total_regressions,
534
+ total_stable,
535
+ total_unstable,
536
+ total_comparison_points,
537
+ )
538
+
539
+
540
+ def check_regression_comment(comments):
541
+ res = False
542
+ pos = -1
543
+ for n, comment in enumerate(comments):
544
+ body = comment.body
545
+ if "Comparison between" in body and "Time Period from" in body:
546
+ res = True
547
+ pos = n
548
+ return res, pos
549
+
550
+
551
+ def compute_regression_table(
552
+ rts,
553
+ tf_github_org,
554
+ tf_github_repo,
555
+ tf_triggering_env,
556
+ metric_name,
557
+ comparison_branch,
558
+ baseline_branch="master",
559
+ baseline_tag=None,
560
+ comparison_tag=None,
561
+ baseline_deployment_name="oss-standalone",
562
+ comparison_deployment_name="oss-standalone",
563
+ print_improvements_only=False,
564
+ print_regressions_only=False,
565
+ skip_unstable=False,
566
+ regressions_percent_lower_limit=5.0,
567
+ simplify_table=False,
568
+ test="",
569
+ testname_regex=".*",
570
+ verbose=False,
571
+ last_n_baseline=-1,
572
+ last_n_comparison=-1,
573
+ metric_mode="higher-better",
574
+ from_date=None,
575
+ from_ts_ms=None,
576
+ to_date=None,
577
+ to_ts_ms=None,
578
+ use_metric_context_path=None,
579
+ running_platform=None,
580
+ ):
581
+ START_TIME_NOW_UTC, _, _ = get_start_time_vars()
582
+ START_TIME_LAST_MONTH_UTC = START_TIME_NOW_UTC - datetime.timedelta(days=31)
583
+ if from_date is None:
584
+ from_date = START_TIME_LAST_MONTH_UTC
585
+ if to_date is None:
586
+ to_date = START_TIME_NOW_UTC
587
+ if from_ts_ms is None:
588
+ from_ts_ms = int(from_date.timestamp() * 1000)
589
+ if to_ts_ms is None:
590
+ to_ts_ms = int(to_date.timestamp() * 1000)
591
+ from_human_str = humanize.naturaltime(
592
+ dt.datetime.utcfromtimestamp(from_ts_ms / 1000)
593
+ )
594
+ to_human_str = humanize.naturaltime(dt.datetime.utcfromtimestamp(to_ts_ms / 1000))
595
+ logging.info(
596
+ "Using a time-delta from {} to {}".format(from_human_str, to_human_str)
597
+ )
598
+ baseline_str, by_str_baseline, comparison_str, by_str_comparison = get_by_strings(
599
+ baseline_branch,
600
+ comparison_branch,
601
+ baseline_tag,
602
+ comparison_tag,
603
+ )
604
+ (
605
+ prefix,
606
+ testcases_setname,
607
+ _,
608
+ tsname_project_total_failures,
609
+ tsname_project_total_success,
610
+ _,
611
+ _,
612
+ _,
613
+ testcases_metric_context_path_setname,
614
+ _,
615
+ _,
616
+ _,
617
+ _,
618
+ _,
619
+ ) = get_overall_dashboard_keynames(tf_github_org, tf_github_repo, tf_triggering_env)
620
+ test_names = []
621
+ used_key = testcases_setname
622
+ test_filter = "test_name"
623
+ if use_metric_context_path:
624
+ test_filter = "test_name:metric_context_path"
625
+ used_key = testcases_metric_context_path_setname
626
+ tags_regex_string = re.compile(testname_regex)
627
+ if test != "":
628
+ test_names = test.split(",")
629
+ logging.info("Using test name {}".format(test_names))
630
+ else:
631
+ test_names = get_test_names_from_db(
632
+ rts, tags_regex_string, test_names, used_key
633
+ )
634
+ (
635
+ detected_regressions,
636
+ table,
637
+ total_improvements,
638
+ total_regressions,
639
+ total_stable,
640
+ total_unstable,
641
+ total_comparison_points,
642
+ ) = from_rts_to_regression_table(
643
+ baseline_deployment_name,
644
+ comparison_deployment_name,
645
+ baseline_str,
646
+ comparison_str,
647
+ by_str_baseline,
648
+ by_str_comparison,
649
+ from_ts_ms,
650
+ to_ts_ms,
651
+ last_n_baseline,
652
+ last_n_comparison,
653
+ metric_mode,
654
+ metric_name,
655
+ print_improvements_only,
656
+ print_regressions_only,
657
+ skip_unstable,
658
+ regressions_percent_lower_limit,
659
+ rts,
660
+ simplify_table,
661
+ test_filter,
662
+ test_names,
663
+ tf_triggering_env,
664
+ verbose,
665
+ running_platform,
666
+ )
667
+ logging.info(
668
+ "Printing differential analysis between {} and {}".format(
669
+ baseline_str, comparison_str
670
+ )
671
+ )
672
+ writer = MarkdownTableWriter(
673
+ table_name="Comparison between {} and {}.\n\nTime Period from {}. (environment used: {})\n".format(
674
+ baseline_str,
675
+ comparison_str,
676
+ from_human_str,
677
+ baseline_deployment_name,
678
+ ),
679
+ headers=[
680
+ "Test Case",
681
+ "Baseline {} (median obs. +- std.dev)".format(baseline_str),
682
+ "Comparison {} (median obs. +- std.dev)".format(comparison_str),
683
+ "% change ({})".format(metric_mode),
684
+ "Note",
685
+ ],
686
+ value_matrix=table,
687
+ )
688
+ table_output = ""
689
+
690
+ from io import StringIO
691
+ import sys
692
+
693
+ old_stdout = sys.stdout
694
+ sys.stdout = mystdout = StringIO()
695
+
696
+ writer.dump(mystdout, False)
697
+
698
+ sys.stdout = old_stdout
699
+
700
+ table_output = mystdout.getvalue()
701
+
702
+ return (
703
+ detected_regressions,
704
+ table_output,
705
+ total_improvements,
706
+ total_regressions,
707
+ total_stable,
708
+ total_unstable,
709
+ total_comparison_points,
710
+ )
711
+
712
+
713
+ def get_by_strings(
714
+ baseline_branch,
715
+ comparison_branch,
716
+ baseline_tag,
717
+ comparison_tag,
718
+ ):
719
+ baseline_covered = False
720
+ comparison_covered = False
721
+ by_str_baseline = ""
722
+ by_str_comparison = ""
723
+ baseline_str = ""
724
+ comparison_str = ""
725
+ if baseline_branch is not None:
726
+ baseline_covered = True
727
+ by_str_baseline = "branch"
728
+ baseline_str = baseline_branch
729
+ if comparison_branch is not None:
730
+ comparison_covered = True
731
+ by_str_comparison = "branch"
732
+ comparison_str = comparison_branch
733
+
734
+ if baseline_tag is not None:
735
+ if comparison_covered:
736
+ logging.error(
737
+ "--baseline-branch and --baseline-tag are mutually exclusive. Pick one..."
738
+ )
739
+ exit(1)
740
+ baseline_covered = True
741
+ by_str_baseline = "version"
742
+ baseline_str = baseline_tag
743
+
744
+ if comparison_tag is not None:
745
+ # check if we had already covered comparison
746
+ if comparison_covered:
747
+ logging.error(
748
+ "--comparison-branch and --comparison-tag are mutually exclusive. Pick one..."
749
+ )
750
+ exit(1)
751
+ comparison_covered = True
752
+ by_str_comparison = "version"
753
+ comparison_str = comparison_tag
754
+
755
+ if baseline_covered is False:
756
+ logging.error(
757
+ "You need to provider either " + "( --baseline-branch or --baseline-tag ) "
758
+ )
759
+ exit(1)
760
+ if comparison_covered is False:
761
+ logging.error(
762
+ "You need to provider either "
763
+ + "( --comparison-branch or --comparison-tag ) "
764
+ )
765
+ exit(1)
766
+ return baseline_str, by_str_baseline, comparison_str, by_str_comparison
767
+
768
+
769
+ def from_rts_to_regression_table(
770
+ baseline_deployment_name,
771
+ comparison_deployment_name,
772
+ baseline_str,
773
+ comparison_str,
774
+ by_str_baseline,
775
+ by_str_comparison,
776
+ from_ts_ms,
777
+ to_ts_ms,
778
+ last_n_baseline,
779
+ last_n_comparison,
780
+ metric_mode,
781
+ metric_name,
782
+ print_improvements_only,
783
+ print_regressions_only,
784
+ skip_unstable,
785
+ regressions_percent_lower_limit,
786
+ rts,
787
+ simplify_table,
788
+ test_filter,
789
+ test_names,
790
+ tf_triggering_env,
791
+ verbose,
792
+ running_platform=None,
793
+ ):
794
+ print_all = print_regressions_only is False and print_improvements_only is False
795
+ table = []
796
+ detected_regressions = []
797
+ total_improvements = 0
798
+ total_stable = 0
799
+ total_unstable = 0
800
+ total_regressions = 0
801
+ total_comparison_points = 0
802
+ noise_waterline = 3
803
+ progress = tqdm(unit="benchmark time-series", total=len(test_names))
804
+ at_comparison = 0
805
+ for test_name in test_names:
806
+ multi_value_baseline = check_multi_value_filter(baseline_str)
807
+ multi_value_comparison = check_multi_value_filter(comparison_str)
808
+
809
+ filters_baseline = [
810
+ "{}={}".format(by_str_baseline, baseline_str),
811
+ "metric={}".format(metric_name),
812
+ "{}={}".format(test_filter, test_name),
813
+ "deployment_name={}".format(baseline_deployment_name),
814
+ "triggering_env={}".format(tf_triggering_env),
815
+ ]
816
+ if running_platform is not None:
817
+ filters_baseline.append("running_platform={}".format(running_platform))
818
+ filters_comparison = [
819
+ "{}={}".format(by_str_comparison, comparison_str),
820
+ "metric={}".format(metric_name),
821
+ "{}={}".format(test_filter, test_name),
822
+ "deployment_name={}".format(comparison_deployment_name),
823
+ "triggering_env={}".format(tf_triggering_env),
824
+ ]
825
+ if running_platform is not None:
826
+ filters_comparison.append("running_platform={}".format(running_platform))
827
+ baseline_timeseries = rts.ts().queryindex(filters_baseline)
828
+ comparison_timeseries = rts.ts().queryindex(filters_comparison)
829
+
830
+ # avoiding target time-series
831
+ comparison_timeseries = [x for x in comparison_timeseries if "target" not in x]
832
+ baseline_timeseries = [x for x in baseline_timeseries if "target" not in x]
833
+ progress.update()
834
+ if verbose:
835
+ logging.info(
836
+ "Baseline timeseries for {}: {}. test={}".format(
837
+ baseline_str, len(baseline_timeseries), test_name
838
+ )
839
+ )
840
+ logging.info(
841
+ "Comparison timeseries for {}: {}. test={}".format(
842
+ comparison_str, len(comparison_timeseries), test_name
843
+ )
844
+ )
845
+ if len(baseline_timeseries) > 1 and multi_value_baseline is False:
846
+ baseline_timeseries = get_only_Totals(baseline_timeseries)
847
+
848
+ if len(baseline_timeseries) != 1 and multi_value_baseline is False:
849
+ if verbose:
850
+ logging.warning(
851
+ "Skipping this test given the value of timeseries !=1. Baseline timeseries {}".format(
852
+ len(baseline_timeseries)
853
+ )
854
+ )
855
+ if len(baseline_timeseries) > 1:
856
+ logging.warning(
857
+ "\t\tTime-series: {}".format(", ".join(baseline_timeseries))
858
+ )
859
+ continue
860
+
861
+ if len(comparison_timeseries) > 1 and multi_value_comparison is False:
862
+ comparison_timeseries = get_only_Totals(comparison_timeseries)
863
+ if len(comparison_timeseries) != 1 and multi_value_comparison is False:
864
+ if verbose:
865
+ logging.warning(
866
+ "Comparison timeseries {}".format(len(comparison_timeseries))
867
+ )
868
+ continue
869
+
870
+ baseline_v = "N/A"
871
+ comparison_v = "N/A"
872
+ baseline_values = []
873
+ baseline_datapoints = []
874
+ comparison_values = []
875
+ comparison_datapoints = []
876
+ percentage_change = 0.0
877
+ baseline_v_str = "N/A"
878
+ comparison_v_str = "N/A"
879
+ largest_variance = 0
880
+ baseline_pct_change = "N/A"
881
+ comparison_pct_change = "N/A"
882
+
883
+ note = ""
884
+ try:
885
+ for ts_name_baseline in baseline_timeseries:
886
+ datapoints_inner = rts.ts().revrange(
887
+ ts_name_baseline, from_ts_ms, to_ts_ms
888
+ )
889
+ baseline_datapoints.extend(datapoints_inner)
890
+ (
891
+ baseline_pct_change,
892
+ baseline_v,
893
+ largest_variance,
894
+ ) = get_v_pct_change_and_largest_var(
895
+ baseline_datapoints,
896
+ baseline_pct_change,
897
+ baseline_v,
898
+ baseline_values,
899
+ largest_variance,
900
+ last_n_baseline,
901
+ verbose,
902
+ )
903
+ for ts_name_comparison in comparison_timeseries:
904
+ datapoints_inner = rts.ts().revrange(
905
+ ts_name_comparison, from_ts_ms, to_ts_ms
906
+ )
907
+ comparison_datapoints.extend(datapoints_inner)
908
+
909
+ (
910
+ comparison_pct_change,
911
+ comparison_v,
912
+ largest_variance,
913
+ ) = get_v_pct_change_and_largest_var(
914
+ comparison_datapoints,
915
+ comparison_pct_change,
916
+ comparison_v,
917
+ comparison_values,
918
+ largest_variance,
919
+ last_n_comparison,
920
+ verbose,
921
+ )
922
+
923
+ waterline = regressions_percent_lower_limit
924
+ if regressions_percent_lower_limit < largest_variance:
925
+ note = "waterline={:.1f}%.".format(largest_variance)
926
+ waterline = largest_variance
927
+
928
+ except redis.exceptions.ResponseError:
929
+ pass
930
+ except ZeroDivisionError as e:
931
+ logging.error("Detected a ZeroDivisionError. {}".format(e.__str__()))
932
+ pass
933
+ unstable = False
934
+ if baseline_v != "N/A" and comparison_v != "N/A":
935
+ if comparison_pct_change > 10.0 or baseline_pct_change > 10.0:
936
+ note = "UNSTABLE (very high variance)"
937
+ unstable = True
938
+
939
+ baseline_v_str = prepare_value_str(
940
+ baseline_pct_change, baseline_v, baseline_values, simplify_table
941
+ )
942
+ comparison_v_str = prepare_value_str(
943
+ comparison_pct_change, comparison_v, comparison_values, simplify_table
944
+ )
945
+
946
+ if metric_mode == "higher-better":
947
+ percentage_change = (
948
+ float(comparison_v) / float(baseline_v) - 1
949
+ ) * 100.0
950
+ else:
951
+ # lower-better
952
+ percentage_change = (
953
+ float(baseline_v) / float(comparison_v) - 1
954
+ ) * 100.0
955
+ if baseline_v != "N/A" or comparison_v != "N/A":
956
+ detected_regression = False
957
+ detected_improvement = False
958
+ if percentage_change < 0.0 and not unstable:
959
+ if -waterline >= percentage_change:
960
+ detected_regression = True
961
+ total_regressions = total_regressions + 1
962
+ note = note + " REGRESSION"
963
+ detected_regressions.append(test_name)
964
+ elif percentage_change < -noise_waterline:
965
+ if simplify_table is False:
966
+ note = note + " potential REGRESSION"
967
+ else:
968
+ if simplify_table is False:
969
+ note = note + " No Change"
970
+
971
+ if percentage_change > 0.0 and not unstable:
972
+ if percentage_change > waterline:
973
+ detected_improvement = True
974
+ total_improvements = total_improvements + 1
975
+ note = note + " IMPROVEMENT"
976
+ elif percentage_change > noise_waterline:
977
+ if simplify_table is False:
978
+ note = note + " potential IMPROVEMENT"
979
+ else:
980
+ if simplify_table is False:
981
+ note = note + " No Change"
982
+
983
+ if (
984
+ detected_improvement is False
985
+ and detected_regression is False
986
+ and not unstable
987
+ ):
988
+ total_stable = total_stable + 1
989
+
990
+ if unstable:
991
+ total_unstable += 1
992
+
993
+ should_add_line = False
994
+ if print_regressions_only and detected_regression:
995
+ should_add_line = True
996
+ if print_improvements_only and detected_improvement:
997
+ should_add_line = True
998
+ if print_all:
999
+ should_add_line = True
1000
+ if unstable and skip_unstable:
1001
+ should_add_line = False
1002
+
1003
+ if should_add_line:
1004
+ total_comparison_points = total_comparison_points + 1
1005
+ add_line(
1006
+ baseline_v_str,
1007
+ comparison_v_str,
1008
+ note,
1009
+ percentage_change,
1010
+ table,
1011
+ test_name,
1012
+ )
1013
+ return (
1014
+ detected_regressions,
1015
+ table,
1016
+ total_improvements,
1017
+ total_regressions,
1018
+ total_stable,
1019
+ total_unstable,
1020
+ total_comparison_points,
1021
+ )
1022
+
1023
+
1024
+ def get_only_Totals(baseline_timeseries):
1025
+ logging.warning("\t\tTime-series: {}".format(", ".join(baseline_timeseries)))
1026
+ logging.info("Checking if Totals will reduce timeseries.")
1027
+ new_base = []
1028
+ for ts_name in baseline_timeseries:
1029
+ if "Totals" in ts_name:
1030
+ new_base.append(ts_name)
1031
+ baseline_timeseries = new_base
1032
+ return baseline_timeseries
1033
+
1034
+
1035
+ def check_multi_value_filter(baseline_str):
1036
+ multi_value_baseline = False
1037
+ if "(" in baseline_str and "," in baseline_str and ")" in baseline_str:
1038
+ multi_value_baseline = True
1039
+ return multi_value_baseline
1040
+
1041
+
1042
+ def prepare_value_str(baseline_pct_change, baseline_v, baseline_values, simplify_table):
1043
+ if baseline_v < 1.0:
1044
+ baseline_v_str = " {:.2f}".format(baseline_v)
1045
+ elif baseline_v < 10.0:
1046
+ baseline_v_str = " {:.1f}".format(baseline_v)
1047
+ else:
1048
+ baseline_v_str = " {:.0f}".format(baseline_v)
1049
+ stamp_b = ""
1050
+ if baseline_pct_change > 10.0:
1051
+ stamp_b = "UNSTABLE "
1052
+ if len(baseline_values) > 1:
1053
+ baseline_v_str += " +- {:.1f}% {}".format(
1054
+ baseline_pct_change,
1055
+ stamp_b,
1056
+ )
1057
+ if simplify_table is False and len(baseline_values) > 1:
1058
+ baseline_v_str += "({} datapoints)".format(len(baseline_values))
1059
+ return baseline_v_str
1060
+
1061
+
1062
+ def get_test_names_from_db(rts, tags_regex_string, test_names, used_key):
1063
+ try:
1064
+ test_names = rts.smembers(used_key)
1065
+ test_names = list(test_names)
1066
+ test_names.sort()
1067
+ final_test_names = []
1068
+ for test_name in test_names:
1069
+ test_name = test_name.decode()
1070
+ match_obj = re.search(tags_regex_string, test_name)
1071
+ if match_obj is not None:
1072
+ final_test_names.append(test_name)
1073
+ test_names = final_test_names
1074
+
1075
+ except redis.exceptions.ResponseError as e:
1076
+ logging.warning(
1077
+ "Error while trying to fetch test cases set (key={}) {}. ".format(
1078
+ used_key, e.__str__()
1079
+ )
1080
+ )
1081
+ pass
1082
+ logging.warning(
1083
+ "Based on test-cases set (key={}) we have {} comparison points. ".format(
1084
+ used_key, len(test_names)
1085
+ )
1086
+ )
1087
+ return test_names
1088
+
1089
+
1090
+ def add_line(
1091
+ baseline_v_str,
1092
+ comparison_v_str,
1093
+ note,
1094
+ percentage_change,
1095
+ table,
1096
+ test_name,
1097
+ ):
1098
+ percentage_change_str = "{:.1f}% ".format(percentage_change)
1099
+ table.append(
1100
+ [
1101
+ test_name,
1102
+ baseline_v_str,
1103
+ comparison_v_str,
1104
+ percentage_change_str,
1105
+ note.strip(),
1106
+ ]
1107
+ )
1108
+
1109
+
1110
+ def get_v_pct_change_and_largest_var(
1111
+ comparison_datapoints,
1112
+ comparison_pct_change,
1113
+ comparison_v,
1114
+ comparison_values,
1115
+ largest_variance,
1116
+ last_n=-1,
1117
+ verbose=False,
1118
+ ):
1119
+ comparison_nsamples = len(comparison_datapoints)
1120
+ if comparison_nsamples > 0:
1121
+ _, comparison_v = comparison_datapoints[0]
1122
+ for tuple in comparison_datapoints:
1123
+ if last_n < 0 or (last_n > 0 and len(comparison_values) < last_n):
1124
+ comparison_values.append(tuple[1])
1125
+ comparison_df = pd.DataFrame(comparison_values)
1126
+ comparison_median = float(comparison_df.median())
1127
+ comparison_v = comparison_median
1128
+ comparison_std = float(comparison_df.std())
1129
+ if verbose:
1130
+ logging.info(
1131
+ "comparison_datapoints: {} value: {}; std-dev: {}; median: {}".format(
1132
+ comparison_datapoints,
1133
+ comparison_v,
1134
+ comparison_std,
1135
+ comparison_median,
1136
+ )
1137
+ )
1138
+ comparison_pct_change = (comparison_std / comparison_median) * 100.0
1139
+ if comparison_pct_change > largest_variance:
1140
+ largest_variance = comparison_pct_change
1141
+ return comparison_pct_change, comparison_v, largest_variance
1142
+
1143
+
1144
+ def main():
1145
+ _, _, project_version = populate_with_poetry_data()
1146
+ project_name = "redis-benchmarks-spec-cli"
1147
+ parser = argparse.ArgumentParser(
1148
+ description=get_version_string(project_name, project_version),
1149
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1150
+ )
1151
+ parser = create_compare_arguments(parser)
1152
+ args = parser.parse_args()
1153
+ compare_command_logic(args, project_name, project_version)