limits 5.0.0rc1__tar.gz → 5.0.0rc2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {limits-5.0.0rc1 → limits-5.0.0rc2}/HISTORY.rst +26 -0
  2. {limits-5.0.0rc1 → limits-5.0.0rc2}/PKG-INFO +2 -1
  3. {limits-5.0.0rc1 → limits-5.0.0rc2}/README.rst +1 -0
  4. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/conf.py +12 -1
  5. limits-5.0.0rc2/doc/source/ext/_static/benchmark-chart.css +94 -0
  6. limits-5.0.0rc2/doc/source/ext/_static/js/benchmark-chart.js +489 -0
  7. limits-5.0.0rc2/doc/source/ext/_static/js/benchmark-details.js +117 -0
  8. limits-5.0.0rc2/doc/source/ext/_static/js/benchmark-loader.js +43 -0
  9. limits-5.0.0rc2/doc/source/ext/_templates/git_info.js +22 -0
  10. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/ext/bench_chart.py +15 -9
  11. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/installation.rst +6 -1
  12. limits-5.0.0rc2/doc/source/performance.rst +219 -0
  13. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/theme_config.py +10 -0
  14. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/_version.py +3 -3
  15. limits-5.0.0rc1/limits/aio/storage/memcached.py → limits-5.0.0rc2/limits/aio/storage/memcached/__init__.py +43 -113
  16. limits-5.0.0rc2/limits/aio/storage/memcached/bridge.py +73 -0
  17. limits-5.0.0rc2/limits/aio/storage/memcached/emcache.py +112 -0
  18. limits-5.0.0rc2/limits/aio/storage/memcached/memcachio.py +104 -0
  19. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/memory.py +29 -18
  20. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/memcached.py +2 -0
  21. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/memory.py +5 -5
  22. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/typing.py +1 -0
  23. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/PKG-INFO +2 -1
  24. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/SOURCES.txt +4 -1
  25. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/docs.txt +3 -2
  26. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/test.txt +1 -1
  27. {limits-5.0.0rc1 → limits-5.0.0rc2}/setup.cfg +3 -0
  28. limits-5.0.0rc1/doc/source/ext/_static/benchmark-chart.css +0 -61
  29. limits-5.0.0rc1/doc/source/ext/_static/js/benchmark-chart.js +0 -219
  30. limits-5.0.0rc1/doc/source/ext/_static/js/benchmark-details.js +0 -103
  31. limits-5.0.0rc1/doc/source/ext/_static/js/benchmark-loader.js +0 -31
  32. limits-5.0.0rc1/doc/source/ext/_templates/git_info.js +0 -2
  33. limits-5.0.0rc1/doc/source/performance.rst +0 -221
  34. {limits-5.0.0rc1 → limits-5.0.0rc2}/CLASSIFIERS +0 -0
  35. {limits-5.0.0rc1 → limits-5.0.0rc2}/CONTRIBUTIONS.rst +0 -0
  36. {limits-5.0.0rc1 → limits-5.0.0rc2}/LICENSE.txt +0 -0
  37. {limits-5.0.0rc1 → limits-5.0.0rc2}/MANIFEST.in +0 -0
  38. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/Makefile +0 -0
  39. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/_static/custom.css +0 -0
  40. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/api.rst +0 -0
  41. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/async.rst +0 -0
  42. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/changelog.rst +0 -0
  43. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/custom-storage.rst +0 -0
  44. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/index.rst +0 -0
  45. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/quickstart.rst +0 -0
  46. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/storage.rst +0 -0
  47. {limits-5.0.0rc1 → limits-5.0.0rc2}/doc/source/strategies.rst +0 -0
  48. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/__init__.py +0 -0
  49. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/__init__.py +0 -0
  50. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/__init__.py +0 -0
  51. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/base.py +0 -0
  52. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/mongodb.py +0 -0
  53. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/redis/__init__.py +0 -0
  54. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/redis/bridge.py +0 -0
  55. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/redis/coredis.py +0 -0
  56. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/redis/redispy.py +0 -0
  57. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/storage/redis/valkey.py +0 -0
  58. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/aio/strategies.py +0 -0
  59. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/errors.py +0 -0
  60. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/limits.py +0 -0
  61. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/py.typed +0 -0
  62. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
  63. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
  64. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
  65. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
  66. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
  67. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
  68. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/__init__.py +0 -0
  69. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/base.py +0 -0
  70. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/mongodb.py +0 -0
  71. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/redis.py +0 -0
  72. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/redis_cluster.py +0 -0
  73. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/redis_sentinel.py +0 -0
  74. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/storage/registry.py +0 -0
  75. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/strategies.py +0 -0
  76. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/util.py +0 -0
  77. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits/version.py +0 -0
  78. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/dependency_links.txt +0 -0
  79. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/not-zip-safe +0 -0
  80. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/requires.txt +0 -0
  81. {limits-5.0.0rc1 → limits-5.0.0rc2}/limits.egg-info/top_level.txt +0 -0
  82. {limits-5.0.0rc1 → limits-5.0.0rc2}/pyproject.toml +0 -0
  83. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/ci.txt +0 -0
  84. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/dev.txt +0 -0
  85. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/main.txt +0 -0
  86. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/async-memcached.txt +0 -0
  87. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/async-mongodb.txt +0 -0
  88. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/async-redis.txt +0 -0
  89. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/async-valkey.txt +0 -0
  90. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/memcached.txt +0 -0
  91. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/mongodb.txt +0 -0
  92. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/redis.txt +0 -0
  93. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/rediscluster.txt +0 -0
  94. {limits-5.0.0rc1 → limits-5.0.0rc2}/requirements/storage/valkey.txt +0 -0
  95. {limits-5.0.0rc1 → limits-5.0.0rc2}/setup.py +0 -0
  96. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_limit_granularities.py +0 -0
  97. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_limits.py +0 -0
  98. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_ratelimit_parser.py +0 -0
  99. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_storage.py +0 -0
  100. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_strategy.py +0 -0
  101. {limits-5.0.0rc1 → limits-5.0.0rc2}/tests/test_utils.py +0 -0
  102. {limits-5.0.0rc1 → limits-5.0.0rc2}/versioneer.py +0 -0
@@ -3,6 +3,20 @@
3
3
  Changelog
4
4
  =========
5
5
 
6
+ v5.0.0rc2
7
+ ---------
8
+ Release Date: 2025-04-15
9
+
10
+ * Compatibility
11
+
12
+ * Add back emcache as a non default implementation for memcached + asyncio
13
+ * Remove support for memcached < 1.5
14
+
15
+ * Documentation
16
+
17
+ * Improve presentation of benchmark documentation
18
+
19
+
6
20
  v5.0.0rc1
7
21
  ---------
8
22
  Release Date: 2025-04-09
@@ -19,6 +33,18 @@ Release Date: 2025-04-09
19
33
  * Improved performance of redis moving window ``test`` and ``get_window_stats`` operations.
20
34
  * Improved performance of mongodb moving window ``test`` and ``get_window_stats`` operations.
21
35
 
36
+ v4.7.3
37
+ ------
38
+ Release Date: 2025-04-12
39
+
40
+ * Documentation
41
+
42
+ * Expand benchmark results to included preseeded limits
43
+
44
+ * Bug Fix
45
+
46
+ * Handle clearing missing key with memcache + async
47
+
22
48
  v4.7.2
23
49
  ------
24
50
  Release Date: 2025-04-09
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 5.0.0rc1
3
+ Version: 5.0.0rc2
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -274,5 +274,6 @@ Links
274
274
  =====
275
275
 
276
276
  * `Documentation <http://limits.readthedocs.org/en/latest>`_
277
+ * `Benchmarks <http://limits.readthedocs.org/en/latest/performance.html>`_
277
278
  * `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
278
279
 
@@ -207,5 +207,6 @@ Links
207
207
  =====
208
208
 
209
209
  * `Documentation <http://limits.readthedocs.org/en/latest>`_
210
+ * `Benchmarks <http://limits.readthedocs.org/en/latest/performance.html>`_
210
211
  * `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
211
212
 
@@ -16,10 +16,12 @@ project = "limits"
16
16
  description = "limits is a python library to perform rate limiting with commonly used storage backends"
17
17
  copyright = "2023, Ali-Akber Saifee"
18
18
 
19
+
19
20
  if ".post0.dev" in limits.__version__:
20
21
  version, ahead = limits.__version__.split(".post0.dev")
21
22
  else:
22
23
  version = limits.__version__
24
+ ahead = 0
23
25
 
24
26
  release = version
25
27
 
@@ -38,7 +40,15 @@ else:
38
40
  "branch": git_info.get("branch", ""),
39
41
  "sha": git_info.get("long", None),
40
42
  }
41
-
43
+ benchmark_param_mapping = {
44
+ "percentage_full": {
45
+ "display": "Percentage Seeded",
46
+ "info": "Percentage of rate limit already filled before the benchmark is run",
47
+ },
48
+ "async": {"display": "Asyncio", "info": "Using asyncio storage implementation"},
49
+ "storage_type": {"display": "Storage", "info": "Storage Backend"},
50
+ "strategy": {"display": "Strategy", "info": "Rate Limiting Strategy"},
51
+ }
42
52
  html_static_path = ["_static"]
43
53
 
44
54
  html_css_files = [
@@ -73,6 +83,7 @@ extensions = [
73
83
  "sphinxext.opengraph",
74
84
  "sphinxcontrib.programoutput",
75
85
  "sphinx_copybutton",
86
+ "sphinx_design",
76
87
  "sphinx_inline_tabs",
77
88
  "sphinx_paramlinks",
78
89
  "bench_chart",
@@ -0,0 +1,94 @@
1
+ .benchmark-chart {
2
+ width: 100%;
3
+ }
4
+ .benchmark-details {
5
+ .benchmark-details-section {
6
+ thead > tr {
7
+ color: var(--color-purple);
8
+ }
9
+ }
10
+ }
11
+ .benchmark-chart-error,
12
+ .benchmark-details-error {
13
+ color: var(--color-red);
14
+ font-size: smaller;
15
+ padding: 2em;
16
+ text-align: center;
17
+ height: 5em;
18
+ }
19
+ .benchmark-chart-loading {
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ font-size: 1.2rem;
24
+ font-weight: bold;
25
+ color: var(--color-purple);
26
+ letter-spacing: 1px;
27
+ height: 200px;
28
+ animation: benchmark-chart-loading-animation;
29
+ animation-iteration-count: 10;
30
+ animation-duration: 2s;
31
+ }
32
+
33
+ @keyframes benchmark-chart-loading-animation {
34
+ 0% {
35
+ opacity: 0;
36
+ }
37
+ 25% {
38
+ opacity: 0.25;
39
+ }
40
+ 50% {
41
+ opacity: 0.5;
42
+ }
43
+ 90% {
44
+ opacity: 0.9;
45
+ }
46
+ 100% {
47
+ opacity: 1;
48
+ }
49
+ }
50
+ .benchmark-filters {
51
+ padding-top: 1em;
52
+ padding-bottom: 1em;
53
+ display: flex;
54
+ justify-content: flex-end;
55
+ }
56
+ .benchmark-filter-dropdowns {
57
+ display: flex;
58
+ align-items: flex-end;
59
+ justify-content: flex-end;
60
+ margin-top: 2em;
61
+ flex-direction: row;
62
+ @media only screen and (max-width: 800px) {
63
+ flex-basis: min-content;
64
+ }
65
+ @media only screen and (max-width: 400px) {
66
+ flex-basis: max-content;
67
+ flex-direction: column;
68
+ }
69
+ }
70
+
71
+ .compare-dropdown {
72
+ display: flex;
73
+ justify-content: flex-end;
74
+ }
75
+
76
+ .benchmark-filter {
77
+ display: flex;
78
+ flex-direction: row;
79
+ gap: 0.25em;
80
+ padding: 0.25em;
81
+ }
82
+
83
+ .benchmark-filter label,
84
+ .compare-filter label {
85
+ font-size: small;
86
+ font-weight: bold;
87
+ }
88
+
89
+ .benchmark-filter select,
90
+ .compare-filter select {
91
+ font-size: small;
92
+ border: 1px solid;
93
+ border-radius: 0.4em;
94
+ }
@@ -0,0 +1,489 @@
1
+ import { render, html } from "https://unpkg.com/uhtml@3.2.1?module";
2
+ import { BenchmarkLoader } from "./benchmark-loader.js";
3
+ const KNOWN_PARAMS = [
4
+ "storage_type",
5
+ "limit",
6
+ "strategy",
7
+ "async",
8
+ "percentage_full",
9
+ ];
10
+ let dispatched = new Set();
11
+ window.Benchmarks = {};
12
+ let currentLoader = new BenchmarkLoader();
13
+ let compareLoaders = {};
14
+
15
+ class Utils {
16
+ static getBenchmarkData(result, query) {
17
+ let benchmarks = result.benchmarks;
18
+ return benchmarks.filter(function (benchmark) {
19
+ let okay = true;
20
+ if (query) {
21
+ Object.entries(query).forEach((entry) => {
22
+ let key = entry[0];
23
+ let value = entry[1];
24
+ if (
25
+ key != "group" &&
26
+ !(value === "") && // i.e. any.
27
+ benchmark.params[key] != null &&
28
+ benchmark.params[key] != value
29
+ ) {
30
+ okay = false;
31
+ } else if (key == "group" && value != benchmark.group) {
32
+ okay = false;
33
+ }
34
+ });
35
+ }
36
+ return okay;
37
+ });
38
+ }
39
+
40
+ static formatParam(key, str) {
41
+ if (key === "limit") {
42
+ var m = str.match(/(\d+(?:\.\d+)?)\s+per\s+1\s+(\w+)/i);
43
+ if (!m) return str;
44
+ var n = parseFloat(m[1]),
45
+ u = m[2].toLowerCase(),
46
+ num =
47
+ n >= 1000
48
+ ? (n / 1000) % 1 === 0
49
+ ? n / 1000 + "K"
50
+ : (n / 1000).toFixed(1) + "K"
51
+ : n.toString(),
52
+ umap = {
53
+ second: "s",
54
+ seconds: "s",
55
+ minute: "min",
56
+ minutes: "mins",
57
+ hour: "hr",
58
+ hours: "hr",
59
+ day: "day",
60
+ days: "day",
61
+ };
62
+ return num + "/" + (umap[u] || u);
63
+ } else if (key === "percentage_full") {
64
+ return `${str}% Seeded`;
65
+ } else if (key === "strategy") {
66
+ return str.replaceAll("-", " ").replace(/\b\w/g, (s) => s.toUpperCase());
67
+ } else if (key === "async") {
68
+ return str == true ? "asyncio" : "sync";
69
+ }
70
+ return str;
71
+ }
72
+ static nameTransform(benchmark, stripParams, query) {
73
+ let name = benchmark.name;
74
+ let params = benchmark.params;
75
+ name = name
76
+ .replace(/\[.*?\]/, "")
77
+ .replace("_async", "")
78
+ .replaceAll("_", "-");
79
+ name = name.replace(benchmark.group, "");
80
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
81
+ let additional = Utils.getRemainingGroups(benchmark, query);
82
+ Object.entries(additional).forEach((param) => {
83
+ let value = Utils.formatParam(param[0], param[1]);
84
+ if (name) {
85
+ name += ` - ${value}`;
86
+ } else {
87
+ name = `${value}`;
88
+ }
89
+ });
90
+ return name;
91
+ }
92
+
93
+ static getRemainingGroups(benchmark, query) {
94
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
95
+ let additional = {};
96
+ Object.entries(benchmark.params).forEach((param) => {
97
+ const key = param[0];
98
+ const value = param[1];
99
+ if (
100
+ (!queryParam.includes(key) || query?.[key] === "") &&
101
+ KNOWN_PARAMS.includes(key)
102
+ ) {
103
+ additional[key] = value;
104
+ }
105
+ });
106
+ return additional;
107
+ }
108
+ static color(name) {
109
+ return window
110
+ .getComputedStyle(document.body)
111
+ .getPropertyValue(`--color-${name}`);
112
+ }
113
+ static getColorForStorage(storageType) {
114
+ const storageColorMap = {
115
+ memory: this.color("purple"),
116
+ mongodb: this.color("mongodb"),
117
+ memcached: this.color("blue"),
118
+ redis: this.color("redis"),
119
+ };
120
+
121
+ return storageColorMap[storageType] || "#7f7f7f"; // gray
122
+ }
123
+
124
+ static sortBenchmarksByParams(benchmarks, sortKeys) {
125
+ return benchmarks.sort(function (a, b) {
126
+ for (const key of sortKeys) {
127
+ let valA = (a.params?.[key] || "").toLowerCase();
128
+ let valB = (b.params?.[key] || "").toLowerCase();
129
+ if (key === "limit") {
130
+ valA = parseInt(valA.split(" ")[0], 10);
131
+ valB = parseInt(valB.split(" ")[0], 10);
132
+ }
133
+ if (valA < valB) return -1;
134
+ if (valA > valB) return 1;
135
+ }
136
+ return a.name.localeCompare(b.name);
137
+ });
138
+ }
139
+
140
+ static fetchComparisonData(source, comparisonSource) {
141
+ if (!compareLoaders[comparisonSource]) {
142
+ compareLoaders[comparisonSource] = new BenchmarkLoader([
143
+ `https://${comparisonSource.replaceAll(".", "-")}--py-limits.netlify.app/`,
144
+ ]);
145
+ }
146
+ return compareLoaders[comparisonSource].fetchBenchmarkData(source);
147
+ }
148
+ }
149
+
150
+ class BenchmarkChart {
151
+ constructor(node) {
152
+ this.chart = node;
153
+ this.title = this.chart.dataset.title;
154
+ this.source = this.chart.dataset.source;
155
+ this.filters = JSON.parse(this.chart.dataset.filters);
156
+ this.query = JSON.parse(this.chart.dataset.query);
157
+ this.paramMapping = JSON.parse(this.chart.dataset.paramMapping);
158
+ this.chartId = this.chart.dataset.chartId;
159
+ this.currentCompare = null;
160
+ this.sortBy = JSON.parse(
161
+ this.chart.dataset.sortBy || '["storage_type", "limit"]',
162
+ );
163
+ let self = this;
164
+ render(
165
+ this.chart,
166
+ html`
167
+ <div class="benchmark-chart-loading">
168
+ <span>Loading</span>
169
+ </div>
170
+ `,
171
+ );
172
+ if (!dispatched.has(this.source)) {
173
+ currentLoader
174
+ .fetchBenchmarkData(this.source)
175
+ .then((result) => {
176
+ let event = new Event(`${self.source}-loaded`);
177
+ window.dispatchEvent(event);
178
+ })
179
+ .catch((error) => {
180
+ let event = new Event(`${self.source}-failed`);
181
+ window.dispatchEvent(event);
182
+ });
183
+ }
184
+ dispatched.add(this.source);
185
+ window.addEventListener(`${this.source}-failed`, function () {
186
+ self.handleError();
187
+ });
188
+ window.addEventListener(`${this.source}-loaded`, function () {
189
+ self.render();
190
+ });
191
+ }
192
+ handleError() {
193
+ this.chart.querySelector(".benchmark-chart-loading")?.remove();
194
+ render(
195
+ this.chart,
196
+ html`
197
+ <div class="benchmark-chart-error">Benchmark data not available.</div>
198
+ `,
199
+ );
200
+ }
201
+ chartTitle(query) {
202
+ let selectedFilters = Object.entries(query).filter(
203
+ (entry) =>
204
+ entry[1] != null &&
205
+ entry[1] != "" &&
206
+ !this.query.hasOwnProperty(entry[0]),
207
+ );
208
+ let derivedTitle = selectedFilters.reduce(
209
+ (title, entry) =>
210
+ (title += `${title == "" ? "" : ", "}${Utils.formatParam(entry[0], entry[1])}`),
211
+ "",
212
+ );
213
+ if (this.title != "") {
214
+ return `${this.title}<br>${derivedTitle}`;
215
+ } else {
216
+ return derivedTitle;
217
+ }
218
+ }
219
+ renderCompare() {
220
+ const compareTargets = ["master", "stable", "4.x"]
221
+ .concat(window.LATEST_RELEASES || [])
222
+ .filter((element) => element != window.GITBRANCH);
223
+ let self = this;
224
+ const compareDropdown = html`
225
+ <div
226
+ class="compare-filter"
227
+ title="Compare against another release or branch"
228
+ >
229
+ <label>
230
+ Compare with
231
+ <select
232
+ onchange=${(e) => {
233
+ const value = e.target.value;
234
+ self.currentCompare = value;
235
+
236
+ if (value !== "") {
237
+ Utils.fetchComparisonData(self.source, value).then(
238
+ function (comparisonData) {
239
+ self.currentComparisonData = comparisonData;
240
+ self.renderChartWithFilters();
241
+ },
242
+ );
243
+ } else {
244
+ self.currentComparisonData = null;
245
+ self.renderChartWithFilters();
246
+ }
247
+ let event = new Event("benchmark-compare-changed");
248
+ event.compare = self.currentCompare;
249
+ event.source = self;
250
+ window.dispatchEvent(event);
251
+ }}
252
+ >
253
+ <option value=""></option>
254
+ ${compareTargets.map(
255
+ (val) => html`
256
+ <option
257
+ value=${val}
258
+ ?selected=${this.currentCompare == val.toString()}
259
+ >
260
+ ${val}
261
+ </option>
262
+ `,
263
+ )}
264
+ </select>
265
+ </label>
266
+ </div>
267
+ `;
268
+ window.addEventListener("benchmark-compare-changed", function (event) {
269
+ let select = self.compareTarget.getElementsByTagName("select")[0];
270
+ if (event.source != self && select.value != event.compare) {
271
+ select.value = event.compare;
272
+ select.dispatchEvent(new Event("change"));
273
+ }
274
+ });
275
+ render(
276
+ this.compareTarget,
277
+ html`<div class="compare-dropdown">${compareDropdown}</div>`,
278
+ );
279
+ }
280
+ renderDropdowns() {
281
+ const dropdowns = Object.entries(this.filters).map(([key]) => {
282
+ const fullName = `${this.chartId}-${key}`;
283
+ const uniqueValues = [
284
+ ...new Set(this.allBenchmarks.map((b) => b.params?.[key])),
285
+ ].sort();
286
+ const isBoolean =
287
+ uniqueValues.length === 2 &&
288
+ uniqueValues.includes(true) &&
289
+ uniqueValues.includes(false);
290
+ let self = this;
291
+ if (isBoolean) {
292
+ return html`
293
+ <div class="benchmark-filter" title=${this.paramMapping[key]?.info}>
294
+ <input
295
+ type="checkbox"
296
+ id=${fullName}
297
+ ?checked=${self.currentFilters[key] === true}
298
+ onchange=${(e) => {
299
+ self.currentFilters[key] = e.target.checked;
300
+ self.renderChartWithFilters();
301
+ }}
302
+ />
303
+ <label for=${fullName}>
304
+ ${self.paramMapping[key]?.display || key}
305
+ </label>
306
+ </div>
307
+ `;
308
+ } else {
309
+ return html`
310
+ <div class="benchmark-filter" title=${self.paramMapping[key]?.info}>
311
+ <label for=${fullName}>
312
+ ${self.paramMapping[key]?.display || key}
313
+ <select
314
+ id=${fullName}
315
+ onchange=${(e) => {
316
+ const value = e.target.value;
317
+ if (value) {
318
+ self.currentFilters[key] =
319
+ value === "false"
320
+ ? false
321
+ : value === "true"
322
+ ? true
323
+ : value;
324
+ } else {
325
+ self.currentFilters[key] = "";
326
+ }
327
+ self.renderChartWithFilters();
328
+ }}
329
+ >
330
+ <option value="" ?selected=${self.currentFilters[key] == ""}>
331
+ All
332
+ </option>
333
+ ${uniqueValues.map(
334
+ (val) => html`
335
+ <option
336
+ value=${val}
337
+ ?selected=${self.currentFilters[key] == val.toString()}
338
+ >
339
+ ${val}
340
+ </option>
341
+ `,
342
+ )}
343
+ </select>
344
+ </label>
345
+ </div>
346
+ `;
347
+ }
348
+ });
349
+ render(
350
+ this.dropdownTarget,
351
+ html`<div class="benchmark-filter-dropdowns">${dropdowns}</div>`,
352
+ );
353
+ }
354
+ renderChartWithFilters() {
355
+ const queryFilter = { ...this.query, ...this.currentFilters };
356
+ let data = Utils.sortBenchmarksByParams(
357
+ Utils.getBenchmarkData(this.results, queryFilter),
358
+ this.sortBy,
359
+ );
360
+ let comparisonData = [];
361
+ let comparing = false;
362
+ if (this.currentComparisonData?.benchmarks) {
363
+ comparisonData = Utils.sortBenchmarksByParams(
364
+ Utils.getBenchmarkData(this.currentComparisonData, queryFilter),
365
+ this.sortBy,
366
+ );
367
+ comparisonData.forEach((benchmark) => {
368
+ benchmark.forComparison = true;
369
+ });
370
+ comparing = true;
371
+ data = comparisonData.concat(data);
372
+ }
373
+ let legendGroupKey =
374
+ queryFilter?.storage_type == ""
375
+ ? "storage_type"
376
+ : Object.entries(queryFilter).find((entry) => entry[1] === "")?.[0];
377
+
378
+ function legendKeyFunc(benchmark, key) {
379
+ return key === "group" ? benchmark.group : benchmark.params[key];
380
+ }
381
+ let title = this.chartTitle(queryFilter);
382
+ if (comparing) {
383
+ title = `${title}<br><b style='color: var(--color-yellow)'>Compared with: ${this.currentCompare}</b>`;
384
+ }
385
+ Plotly.newPlot(
386
+ this.chartTarget,
387
+ data.map((benchmark) => {
388
+ let name = Utils.nameTransform(benchmark, true, queryFilter);
389
+ return {
390
+ type: "box",
391
+ name: name,
392
+ opacity: benchmark.forComparison ? 0.5 : comparing ? 0.75 : 1,
393
+ y: benchmark.stats.data || [
394
+ benchmark.stats.min * 1e3,
395
+ benchmark.stats.q1 * 1e3,
396
+ benchmark.stats.median * 1e3,
397
+ benchmark.stats.q3 * 1e3,
398
+ benchmark.stats.max * 1e3,
399
+ ],
400
+ boxmean: true,
401
+ boxpoints: false,
402
+ line: { width: 1 },
403
+ marker: {
404
+ color: benchmark.forComparison
405
+ ? Utils.color("yellow")
406
+ : Utils.getColorForStorage(benchmark.params.storage_type),
407
+ },
408
+ showlegend: !benchmark.forComparison,
409
+ legendgroup: legendKeyFunc(benchmark, legendGroupKey),
410
+ legendgrouptitle: {
411
+ text: Utils.formatParam(
412
+ legendGroupKey,
413
+ legendKeyFunc(benchmark, legendGroupKey),
414
+ ),
415
+ },
416
+ };
417
+ }),
418
+ {
419
+ yaxis: {
420
+ title: { text: "Time (ms)" },
421
+ exponentformat: "none",
422
+ ticksuffix: " ms",
423
+ tickformat: ",.2f",
424
+ type: "log",
425
+ autorange: true,
426
+ },
427
+ xaxis: { automargin: true },
428
+ title: {
429
+ text: title,
430
+ },
431
+ paper_bgcolor: Utils.color("background-secondary"),
432
+ plot_bgcolor: Utils.color("background-secondary"),
433
+ font: {
434
+ family: "Fira Sans",
435
+ color: Utils.color("fg"),
436
+ },
437
+ },
438
+ {
439
+ responsive: true,
440
+ displaylogo: false,
441
+ },
442
+ );
443
+ }
444
+ render() {
445
+ this.chart.innerHTML = "";
446
+ this.chart.querySelector(".benchmark-chart-loading")?.remove();
447
+ this.results = currentLoader.sources[this.source];
448
+ this.allBenchmarks = Utils.getBenchmarkData(this.results, this.query);
449
+ this.currentFilters = Object.fromEntries(
450
+ Object.entries(this.filters).map(([key, value]) => {
451
+ return typeof value.default === "boolean"
452
+ ? [key, value.default]
453
+ : [key, value.default != null ? value.default.toString() : ""];
454
+ }),
455
+ );
456
+ this.currentCompare = "";
457
+ this.currentComparisonData = null;
458
+ this.dropdownTarget = document.createElement("div");
459
+ this.compareTarget = document.createElement("div");
460
+ this.dropdownTarget.classList.add("benchmark-filters");
461
+ this.chartTarget = document.createElement("div");
462
+ this.chart.append(this.chartTarget);
463
+ this.chart.append(this.dropdownTarget);
464
+ this.chart.append(this.compareTarget);
465
+ this.renderDropdowns();
466
+ this.renderCompare();
467
+ this.renderChartWithFilters();
468
+ let initial = true;
469
+ this.chartTarget.on("plotly_afterplot", function () {
470
+ const { hash } = window.location;
471
+ if (hash && initial) {
472
+ initial = false;
473
+ const target = document.querySelector(hash);
474
+ if (target) {
475
+ setTimeout(function () {
476
+ target.scrollIntoView({ behavior: "instant" });
477
+ }, 10);
478
+ }
479
+ }
480
+ });
481
+ }
482
+ }
483
+
484
+ document.addEventListener("DOMContentLoaded", function () {
485
+ const charts = document.querySelectorAll(".benchmark-chart");
486
+ charts.forEach((chart) => {
487
+ const chartObject = new BenchmarkChart(chart);
488
+ });
489
+ });