limits 4.7.1__tar.gz → 4.7.3__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 (101) hide show
  1. {limits-4.7.1 → limits-4.7.3}/HISTORY.rst +22 -0
  2. {limits-4.7.1 → limits-4.7.3}/PKG-INFO +1 -1
  3. {limits-4.7.1 → limits-4.7.3}/doc/source/conf.py +10 -1
  4. limits-4.7.3/doc/source/ext/_static/benchmark-chart.css +97 -0
  5. limits-4.7.3/doc/source/ext/_static/js/benchmark-chart.js +343 -0
  6. {limits-4.7.1 → limits-4.7.3}/doc/source/ext/_static/js/benchmark-details.js +22 -6
  7. {limits-4.7.1 → limits-4.7.3}/doc/source/ext/_static/js/benchmark-loader.js +4 -5
  8. limits-4.7.3/doc/source/ext/_templates/git_info.js +2 -0
  9. {limits-4.7.1 → limits-4.7.3}/doc/source/ext/bench_chart.py +26 -12
  10. limits-4.7.3/doc/source/performance.rst +126 -0
  11. {limits-4.7.1 → limits-4.7.3}/limits/_version.py +3 -3
  12. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/memcached.py +4 -1
  13. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/PKG-INFO +1 -1
  14. {limits-4.7.1 → limits-4.7.3}/requirements/docs.txt +3 -3
  15. limits-4.7.1/doc/source/ext/_static/benchmark-chart.css +0 -10
  16. limits-4.7.1/doc/source/ext/_static/js/benchmark-chart.js +0 -194
  17. limits-4.7.1/doc/source/ext/_templates/git_info.js +0 -2
  18. limits-4.7.1/doc/source/performance.rst +0 -221
  19. {limits-4.7.1 → limits-4.7.3}/CLASSIFIERS +0 -0
  20. {limits-4.7.1 → limits-4.7.3}/CONTRIBUTIONS.rst +0 -0
  21. {limits-4.7.1 → limits-4.7.3}/LICENSE.txt +0 -0
  22. {limits-4.7.1 → limits-4.7.3}/MANIFEST.in +0 -0
  23. {limits-4.7.1 → limits-4.7.3}/README.rst +0 -0
  24. {limits-4.7.1 → limits-4.7.3}/doc/Makefile +0 -0
  25. {limits-4.7.1 → limits-4.7.3}/doc/source/_static/custom.css +0 -0
  26. {limits-4.7.1 → limits-4.7.3}/doc/source/api.rst +0 -0
  27. {limits-4.7.1 → limits-4.7.3}/doc/source/async.rst +0 -0
  28. {limits-4.7.1 → limits-4.7.3}/doc/source/changelog.rst +0 -0
  29. {limits-4.7.1 → limits-4.7.3}/doc/source/custom-storage.rst +0 -0
  30. {limits-4.7.1 → limits-4.7.3}/doc/source/index.rst +0 -0
  31. {limits-4.7.1 → limits-4.7.3}/doc/source/installation.rst +0 -0
  32. {limits-4.7.1 → limits-4.7.3}/doc/source/quickstart.rst +0 -0
  33. {limits-4.7.1 → limits-4.7.3}/doc/source/storage.rst +0 -0
  34. {limits-4.7.1 → limits-4.7.3}/doc/source/strategies.rst +0 -0
  35. {limits-4.7.1 → limits-4.7.3}/doc/source/theme_config.py +0 -0
  36. {limits-4.7.1 → limits-4.7.3}/limits/__init__.py +0 -0
  37. {limits-4.7.1 → limits-4.7.3}/limits/aio/__init__.py +0 -0
  38. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/__init__.py +0 -0
  39. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/base.py +0 -0
  40. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/etcd.py +0 -0
  41. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/memory.py +0 -0
  42. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/mongodb.py +0 -0
  43. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/redis/__init__.py +0 -0
  44. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/redis/bridge.py +0 -0
  45. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/redis/coredis.py +0 -0
  46. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/redis/redispy.py +0 -0
  47. {limits-4.7.1 → limits-4.7.3}/limits/aio/storage/redis/valkey.py +0 -0
  48. {limits-4.7.1 → limits-4.7.3}/limits/aio/strategies.py +0 -0
  49. {limits-4.7.1 → limits-4.7.3}/limits/errors.py +0 -0
  50. {limits-4.7.1 → limits-4.7.3}/limits/limits.py +0 -0
  51. {limits-4.7.1 → limits-4.7.3}/limits/py.typed +0 -0
  52. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
  53. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
  54. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
  55. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
  56. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
  57. {limits-4.7.1 → limits-4.7.3}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
  58. {limits-4.7.1 → limits-4.7.3}/limits/storage/__init__.py +0 -0
  59. {limits-4.7.1 → limits-4.7.3}/limits/storage/base.py +0 -0
  60. {limits-4.7.1 → limits-4.7.3}/limits/storage/etcd.py +0 -0
  61. {limits-4.7.1 → limits-4.7.3}/limits/storage/memcached.py +0 -0
  62. {limits-4.7.1 → limits-4.7.3}/limits/storage/memory.py +0 -0
  63. {limits-4.7.1 → limits-4.7.3}/limits/storage/mongodb.py +0 -0
  64. {limits-4.7.1 → limits-4.7.3}/limits/storage/redis.py +0 -0
  65. {limits-4.7.1 → limits-4.7.3}/limits/storage/redis_cluster.py +0 -0
  66. {limits-4.7.1 → limits-4.7.3}/limits/storage/redis_sentinel.py +0 -0
  67. {limits-4.7.1 → limits-4.7.3}/limits/storage/registry.py +0 -0
  68. {limits-4.7.1 → limits-4.7.3}/limits/strategies.py +0 -0
  69. {limits-4.7.1 → limits-4.7.3}/limits/typing.py +0 -0
  70. {limits-4.7.1 → limits-4.7.3}/limits/util.py +0 -0
  71. {limits-4.7.1 → limits-4.7.3}/limits/version.py +0 -0
  72. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/SOURCES.txt +0 -0
  73. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/dependency_links.txt +0 -0
  74. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/not-zip-safe +0 -0
  75. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/requires.txt +0 -0
  76. {limits-4.7.1 → limits-4.7.3}/limits.egg-info/top_level.txt +0 -0
  77. {limits-4.7.1 → limits-4.7.3}/pyproject.toml +0 -0
  78. {limits-4.7.1 → limits-4.7.3}/requirements/ci.txt +0 -0
  79. {limits-4.7.1 → limits-4.7.3}/requirements/dev.txt +0 -0
  80. {limits-4.7.1 → limits-4.7.3}/requirements/main.txt +0 -0
  81. {limits-4.7.1 → limits-4.7.3}/requirements/storage/async-etcd.txt +0 -0
  82. {limits-4.7.1 → limits-4.7.3}/requirements/storage/async-memcached.txt +0 -0
  83. {limits-4.7.1 → limits-4.7.3}/requirements/storage/async-mongodb.txt +0 -0
  84. {limits-4.7.1 → limits-4.7.3}/requirements/storage/async-redis.txt +0 -0
  85. {limits-4.7.1 → limits-4.7.3}/requirements/storage/async-valkey.txt +0 -0
  86. {limits-4.7.1 → limits-4.7.3}/requirements/storage/etcd.txt +0 -0
  87. {limits-4.7.1 → limits-4.7.3}/requirements/storage/memcached.txt +0 -0
  88. {limits-4.7.1 → limits-4.7.3}/requirements/storage/mongodb.txt +0 -0
  89. {limits-4.7.1 → limits-4.7.3}/requirements/storage/redis.txt +0 -0
  90. {limits-4.7.1 → limits-4.7.3}/requirements/storage/rediscluster.txt +0 -0
  91. {limits-4.7.1 → limits-4.7.3}/requirements/storage/valkey.txt +0 -0
  92. {limits-4.7.1 → limits-4.7.3}/requirements/test.txt +0 -0
  93. {limits-4.7.1 → limits-4.7.3}/setup.cfg +0 -0
  94. {limits-4.7.1 → limits-4.7.3}/setup.py +0 -0
  95. {limits-4.7.1 → limits-4.7.3}/tests/test_limit_granularities.py +0 -0
  96. {limits-4.7.1 → limits-4.7.3}/tests/test_limits.py +0 -0
  97. {limits-4.7.1 → limits-4.7.3}/tests/test_ratelimit_parser.py +0 -0
  98. {limits-4.7.1 → limits-4.7.3}/tests/test_storage.py +0 -0
  99. {limits-4.7.1 → limits-4.7.3}/tests/test_strategy.py +0 -0
  100. {limits-4.7.1 → limits-4.7.3}/tests/test_utils.py +0 -0
  101. {limits-4.7.1 → limits-4.7.3}/versioneer.py +0 -0
@@ -3,6 +3,26 @@
3
3
  Changelog
4
4
  =========
5
5
 
6
+ v4.7.3
7
+ ------
8
+ Release Date: 2025-04-12
9
+
10
+ * Documentation
11
+
12
+ * Expand benchmark results to included preseeded limits
13
+
14
+ * Bug Fix
15
+
16
+ * Handle clearing missing key with memcache + async
17
+
18
+ v4.7.2
19
+ ------
20
+ Release Date: 2025-04-09
21
+
22
+ * Documentation
23
+
24
+ * Improve presentation of benchmark docs
25
+
6
26
  v4.7.1
7
27
  ------
8
28
  Release Date: 2025-04-08
@@ -863,6 +883,8 @@ Release Date: 2015-01-08
863
883
 
864
884
 
865
885
 
886
+
887
+
866
888
 
867
889
 
868
890
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 4.7.1
3
+ Version: 4.7.3
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -39,7 +39,15 @@ else:
39
39
  "branch": git_info.get("branch", ""),
40
40
  "sha": git_info.get("long", None)
41
41
  }
42
-
42
+ benchmark_param_mapping = {
43
+ "percentage_full": {
44
+ "display": "Percentage Seeded",
45
+ "info": "Percentage of rate limit already filled before the benchmark is run",
46
+ },
47
+ "async": {"display": "Asyncio", "info": "Using asyncio storage implementation"},
48
+ "storage_type": {"display": "Storage", "info": "Storage Backend"},
49
+ "strategy": {"display": "Strategy", "info": "Rate Limiting Strategy"},
50
+ }
43
51
  html_static_path = ["_static"]
44
52
 
45
53
  html_css_files = [
@@ -73,6 +81,7 @@ extensions = [
73
81
  "sphinxext.opengraph",
74
82
  "sphinxcontrib.programoutput",
75
83
  "sphinx_copybutton",
84
+ "sphinx_design",
76
85
  "sphinx_inline_tabs",
77
86
  "sphinx_paramlinks",
78
87
  "bench_chart",
@@ -0,0 +1,97 @@
1
+ .plot-container {
2
+ filter: invert(100%);
3
+ }
4
+
5
+ body[data-theme="light"] .plot-container {
6
+ filter: invert(0);
7
+ }
8
+
9
+ .benchmark-chart {
10
+ width: 100%;
11
+ }
12
+ .benchmark-details {
13
+ .benchmark-details-section {
14
+ thead > tr {
15
+ color: var(--color-purple);
16
+ }
17
+ }
18
+ }
19
+ .benchmark-chart-error,
20
+ .benchmark-details-error {
21
+ color: var(--color-red);
22
+ font-size: smaller;
23
+ padding: 2em;
24
+ text-align: center;
25
+ height: 5em;
26
+ }
27
+ .benchmark-chart-loading {
28
+ display: flex;
29
+ justify-content: center;
30
+ align-items: center;
31
+ font-size: 1.2rem;
32
+ font-weight: bold;
33
+ color: var(--color-purple);
34
+ letter-spacing: 1px;
35
+ height: 200px;
36
+ animation: benchmark-chart-loading-animation;
37
+ animation-iteration-count: 10;
38
+ animation-duration: 2s;
39
+ }
40
+
41
+ @keyframes benchmark-chart-loading-animation {
42
+ 0% {
43
+ opacity: 0;
44
+ }
45
+ 25% {
46
+ opacity: 0.25;
47
+ }
48
+ 50% {
49
+ opacity: 0.5;
50
+ }
51
+ 90% {
52
+ opacity: 0.9;
53
+ }
54
+ 100% {
55
+ opacity: 1;
56
+ }
57
+ }
58
+ .benchmark-filters {
59
+ padding-top: 1em;
60
+ padding-bottom: 1em;
61
+ display: flex;
62
+ justify-content: flex-end;
63
+ }
64
+ .benchmark-filter-dropdowns {
65
+ display: flex;
66
+ align-items: flex-end;
67
+ justify-content: flex-end;
68
+ margin-top: 2em;
69
+ flex-direction: row;
70
+ @media only screen and (max-width: 800px) {
71
+ flex-basis: min-content;
72
+ }
73
+ @media only screen and (max-width: 400px) {
74
+ flex-basis: max-content;
75
+ flex-direction: column;
76
+ }
77
+ }
78
+
79
+ .benchmark-filter {
80
+ display: flex;
81
+ flex-direction: row;
82
+ gap: 0.25em;
83
+ padding: 0.25em;
84
+ }
85
+
86
+ .benchmark-filter label {
87
+ font-size: small;
88
+ font-weight: bold;
89
+ }
90
+
91
+ .benchmark-filter select {
92
+ font-size: small;
93
+ border: 1px solid;
94
+ border-radius: 0.4em;
95
+ }
96
+ .benchmark-filter input[type="checkbox"] {
97
+ }
@@ -0,0 +1,343 @@
1
+ import { render, html } from "https://unpkg.com/uhtml@3.2.1?module";
2
+ import { fetchBenchmarkData } from "./benchmark-loader.js";
3
+ const KNOWN_PARAMS = [
4
+ "storage_type",
5
+ "limit",
6
+ "strategy",
7
+ "async",
8
+ "percentage_full",
9
+ ];
10
+
11
+ function getBenchmarkData(result, query) {
12
+ let benchmarks = result.benchmarks;
13
+ return benchmarks.filter(function (benchmark) {
14
+ let okay = true;
15
+ if (query) {
16
+ Object.entries(query).forEach((entry) => {
17
+ let key = entry[0];
18
+ let value = entry[1];
19
+ if (
20
+ key != "group" &&
21
+ !(value === "") && // i.e. any.
22
+ benchmark.params[key] != null &&
23
+ benchmark.params[key] != value
24
+ ) {
25
+ okay = false;
26
+ } else if (key == "group" && value != benchmark.group) {
27
+ okay = false;
28
+ }
29
+ });
30
+ }
31
+ return okay;
32
+ });
33
+ }
34
+
35
+ function formatParam(key, str) {
36
+ if (key === "limit") {
37
+ var m = str.match(/(\d+(?:\.\d+)?)\s+per\s+1\s+(\w+)/i);
38
+ if (!m) return str;
39
+ var n = parseFloat(m[1]),
40
+ u = m[2].toLowerCase(),
41
+ num =
42
+ n >= 1000
43
+ ? (n / 1000) % 1 === 0
44
+ ? n / 1000 + "K"
45
+ : (n / 1000).toFixed(1) + "K"
46
+ : n.toString(),
47
+ umap = {
48
+ second: "s",
49
+ seconds: "s",
50
+ minute: "min",
51
+ minutes: "mins",
52
+ hour: "hr",
53
+ hours: "hr",
54
+ day: "day",
55
+ days: "day",
56
+ };
57
+ return num + "/" + (umap[u] || u);
58
+ } else if (key === "percentage_full") {
59
+ return `${str}% Seeded`;
60
+ }
61
+ return str;
62
+ }
63
+
64
+ function nameTransform(benchmark, stripParams, query) {
65
+ let name = benchmark.name;
66
+ let params = benchmark.params;
67
+ name = name
68
+ .replace(/\[.*?\]/, "")
69
+ .replace("_async", "")
70
+ .replaceAll("_", "-");
71
+ name = name.replace(benchmark.group, "");
72
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
73
+ let additional = getRemainingGroups(benchmark, query);
74
+ Object.entries(additional).forEach((param) => {
75
+ let value = formatParam(param[0], param[1]);
76
+ if (name) {
77
+ name += ` - ${value}`;
78
+ } else {
79
+ name = `${value}`;
80
+ }
81
+ });
82
+ return name;
83
+ }
84
+
85
+ function getRemainingGroups(benchmark, query) {
86
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
87
+ let additional = {};
88
+ Object.entries(benchmark.params).forEach((param) => {
89
+ const key = param[0];
90
+ const value = param[1];
91
+ if (
92
+ (!queryParam.includes(key) || query?.[key] === "") &&
93
+ KNOWN_PARAMS.includes(key)
94
+ ) {
95
+ additional[key] = value;
96
+ }
97
+ });
98
+ return additional;
99
+ }
100
+
101
+ function getColorForStorage(storageType) {
102
+ const storageColorMap = {
103
+ memory: window
104
+ .getComputedStyle(document.body)
105
+ .getPropertyValue("--color-purple"),
106
+ mongodb: window
107
+ .getComputedStyle(document.body)
108
+ .getPropertyValue("--color-yellow"),
109
+ memcached: window
110
+ .getComputedStyle(document.body)
111
+ .getPropertyValue("--color-aqua"),
112
+ redis: window
113
+ .getComputedStyle(document.body)
114
+ .getPropertyValue("--color-red"),
115
+ };
116
+
117
+ // Fallback color if an unknown storageType appears
118
+ return storageColorMap[storageType] || "#7f7f7f"; // gray
119
+ }
120
+
121
+ function sortBenchmarksByParams(benchmarks, sortKeys) {
122
+ return benchmarks.sort(function (a, b) {
123
+ for (const key of sortKeys) {
124
+ let valA = (a.params?.[key] || "").toLowerCase();
125
+ let valB = (b.params?.[key] || "").toLowerCase();
126
+ if (key === "limit") {
127
+ valA = parseInt(valA.split(" ")[0], 10);
128
+ valB = parseInt(valB.split(" ")[0], 10);
129
+ }
130
+ if (valA < valB) return -1;
131
+ if (valA > valB) return 1;
132
+ }
133
+ return a.name.localeCompare(b.name);
134
+ });
135
+ }
136
+
137
+ let dispatched = new Set();
138
+
139
+ document.addEventListener("DOMContentLoaded", function () {
140
+ const charts = document.querySelectorAll(".benchmark-chart");
141
+ charts.forEach((chart) => {
142
+ const source = chart.dataset.source;
143
+ const filters = JSON.parse(chart.dataset.filters);
144
+ const query = JSON.parse(chart.dataset.query);
145
+ const paramMapping = JSON.parse(chart.dataset.paramMapping);
146
+ const chartId = chart.dataset.chartId;
147
+ let sortBy = JSON.parse(
148
+ chart.dataset.sortBy || '["storage_type", "limit"]',
149
+ );
150
+ render(
151
+ chart,
152
+ html`
153
+ <div class="benchmark-chart-loading">
154
+ <span>Loading</span>
155
+ </div>
156
+ `,
157
+ );
158
+ if (!dispatched.has(source)) {
159
+ fetchBenchmarkData(`${source}.json`)
160
+ .then((result) => {
161
+ window.Benchmarks[source] = result;
162
+ let event = new Event(`${source}-loaded`);
163
+ window.dispatchEvent(event);
164
+ })
165
+ .catch((error) => {
166
+ let event = new Event(`${source}-failed`);
167
+ window.dispatchEvent(event);
168
+ });
169
+ }
170
+ dispatched.add(source);
171
+ window.addEventListener(`${chart.dataset.source}-failed`, function () {
172
+ chart.querySelector(".benchmark-chart-loading")?.remove();
173
+ render(
174
+ chart,
175
+ html`
176
+ <div class="benchmark-chart-error">Benchmark data not available.</div>
177
+ `,
178
+ );
179
+ });
180
+ window.addEventListener(`${chart.dataset.source}-loaded`, function () {
181
+ chart.innerHTML = "";
182
+ chart.querySelector(".benchmark-chart-loading")?.remove();
183
+ const results = Benchmarks[chart.dataset.source];
184
+ const allBenchmarks = getBenchmarkData(results, query);
185
+ const currentFilters = Object.fromEntries(
186
+ Object.entries(filters).map(([key, value]) => {
187
+ return typeof value.default === "boolean"
188
+ ? [key, value.default]
189
+ : [key, value.default != null ? value.default.toString() : ""];
190
+ }),
191
+ );
192
+ const queryFilter = { ...query, ...currentFilters };
193
+ const dropdownTarget = document.createElement("div");
194
+ dropdownTarget.classList.add("benchmark-filters");
195
+ const chartTarget = document.createElement("div");
196
+ chart.append(chartTarget);
197
+ chart.append(dropdownTarget);
198
+ function renderDropdowns() {
199
+ const dropdowns = Object.entries(filters).map(([key]) => {
200
+ const fullName = `${chartId}-${key}`;
201
+ const uniqueValues = [
202
+ ...new Set(allBenchmarks.map((b) => b.params?.[key])),
203
+ ].sort();
204
+ const isBoolean =
205
+ uniqueValues.length === 2 &&
206
+ uniqueValues.includes(true) &&
207
+ uniqueValues.includes(false);
208
+ if (isBoolean) {
209
+ return html`
210
+ <div class="benchmark-filter" title=${paramMapping[key]?.info}>
211
+ <input
212
+ type="checkbox"
213
+ id=${fullName}
214
+ ?checked=${currentFilters[key] === true}
215
+ onchange=${(e) => {
216
+ currentFilters[key] = e.target.checked;
217
+ renderChartWithFilters(currentFilters);
218
+ }}
219
+ />
220
+ <label for=${fullName}>
221
+ ${paramMapping[key]?.display || key}
222
+ </label>
223
+ </div>
224
+ `;
225
+ } else {
226
+ return html`
227
+ <div class="benchmark-filter" title=${paramMapping[key]?.info}>
228
+ <label for=${fullName}>
229
+ ${paramMapping[key]?.display || key}
230
+ <select
231
+ id=${fullName}
232
+ onchange=${(e) => {
233
+ const value = e.target.value;
234
+ if (value) {
235
+ currentFilters[key] =
236
+ value === "false"
237
+ ? false
238
+ : value === "true"
239
+ ? true
240
+ : value;
241
+ } else {
242
+ currentFilters[key] = "";
243
+ }
244
+ renderChartWithFilters(currentFilters);
245
+ }}
246
+ >
247
+ <option value="" ?selected=${currentFilters[key] == ""}>
248
+ All
249
+ </option>
250
+ ${uniqueValues.map(
251
+ (val) => html`
252
+ <option
253
+ value=${val}
254
+ ?selected=${currentFilters[key] == val.toString()}
255
+ >
256
+ ${val}
257
+ </option>
258
+ `,
259
+ )}
260
+ </select>
261
+ </label>
262
+ </div>
263
+ `;
264
+ }
265
+ });
266
+ render(
267
+ dropdownTarget,
268
+ html`<div class="benchmark-filter-dropdowns">${dropdowns}</div>`,
269
+ );
270
+ }
271
+ function legendKeyFunc(benchmark, key) {
272
+ return key === "group" ? benchmark.group : benchmark.params[key];
273
+ }
274
+ function renderChartWithFilters(currentFilters) {
275
+ const queryFilter = { ...query, ...currentFilters };
276
+ const data = sortBenchmarksByParams(
277
+ getBenchmarkData(results, queryFilter),
278
+ sortBy,
279
+ );
280
+ let legendGroupKey =
281
+ queryFilter?.storage_type == ""
282
+ ? "storage_type"
283
+ : Object.entries(queryFilter).find((entry) => entry[1] === "")?.[0];
284
+ Plotly.newPlot(
285
+ chartTarget,
286
+ data.map((benchmark) => ({
287
+ type: "box",
288
+ name: nameTransform(benchmark, true, queryFilter),
289
+ y: benchmark.stats.data || [
290
+ benchmark.stats.min * 1e3,
291
+ benchmark.stats.q1 * 1e3,
292
+ benchmark.stats.median * 1e3,
293
+ benchmark.stats.q3 * 1e3,
294
+ benchmark.stats.max * 1e3,
295
+ ],
296
+ boxmean: true,
297
+ boxpoints: false,
298
+ line: { width: 1 },
299
+ marker: {
300
+ color: getColorForStorage(benchmark.params.storage_type),
301
+ },
302
+ showlegend: true,
303
+ legendgroup: legendKeyFunc(benchmark, legendGroupKey),
304
+ legendgrouptitle: {
305
+ text: formatParam(
306
+ legendGroupKey,
307
+ legendKeyFunc(benchmark, legendGroupKey),
308
+ ),
309
+ },
310
+ })),
311
+ {
312
+ yaxis: {
313
+ title: { text: "Time (ms)" },
314
+ exponentformat: "none",
315
+ ticksuffix: " ms",
316
+ tickformat: ",.2f",
317
+ },
318
+ },
319
+ {
320
+ responsive: true,
321
+ displaylogo: false,
322
+ },
323
+ );
324
+ }
325
+
326
+ renderDropdowns();
327
+ renderChartWithFilters(currentFilters);
328
+ let initial = true;
329
+ chartTarget.on("plotly_afterplot", function () {
330
+ const { hash } = window.location;
331
+ if (hash && initial) {
332
+ initial = false;
333
+ const target = document.querySelector(hash);
334
+ if (target) {
335
+ setTimeout(function () {
336
+ target.scrollIntoView({ behavior: "instant" });
337
+ }, 10);
338
+ }
339
+ }
340
+ });
341
+ });
342
+ });
343
+ });
@@ -1,21 +1,37 @@
1
1
  import { render, html } from "https://unpkg.com/uhtml@3.2.1?module";
2
+ import { fetchBenchmarkData } from "./benchmark-loader.js";
2
3
  document.addEventListener("DOMContentLoaded", function () {
3
4
  const details = document.querySelectorAll(".benchmark-details");
5
+ let dispatched = new Set();
4
6
  details.forEach((detail) => {
5
7
  let source = detail.dataset.source;
6
8
  if (!dispatched.has(source)) {
7
- fetchBenchmarkData(`${source}.json`).then((result) => {
8
- window.Benchmarks[source] = result;
9
- event = new Event(`${source}-loaded`);
10
- window.dispatchEvent(event);
11
- });
9
+ fetchBenchmarkData(`${source}.json`)
10
+ .then((result) => {
11
+ window.Benchmarks[source] = result;
12
+ let event = new Event(`${source}-loaded`);
13
+ window.dispatchEvent(event);
14
+ })
15
+ .catch((error) => {
16
+ let event = new Event(`${source}-failed`);
17
+ window.dispatchEvent(event);
18
+ });
12
19
  }
13
20
  dispatched.add(source);
21
+ window.addEventListener(`${detail.dataset.source}-failed`, function () {
22
+ render(
23
+ detail,
24
+ html`
25
+ <div class="benchmark-details-error">
26
+ Benchmark data not available.
27
+ </div>
28
+ `,
29
+ );
30
+ });
14
31
  window.addEventListener(`${detail.dataset.source}-loaded`, function () {
15
32
  const machine_info = window.Benchmarks[source].machine_info;
16
33
  const commit_info = window.Benchmarks[source].commit_info;
17
34
  const cpu = window.Benchmarks[source].machine_info.cpu;
18
- console.log(machine_info);
19
35
  render(
20
36
  detail,
21
37
  html`
@@ -7,16 +7,13 @@ window.Benchmarks = new Map();
7
7
 
8
8
  function fetchBenchmarkData(filename) {
9
9
  let attempts = 0;
10
-
11
10
  function tryFetch() {
12
11
  if (attempts >= BENCHMARK_PATHS.length) {
13
- return Promise.reject("All fetch attempts failed.");
12
+ return Promise.reject(new Error("All fetch attempts failed."));
14
13
  }
15
-
16
14
  const base = BENCHMARK_PATHS[attempts++];
17
15
  const url = base + filename;
18
-
19
- // First send a HEAD request to quietly check existence
16
+ console.log(`Testing ${url}`);
20
17
  return fetch(url, { method: "HEAD" })
21
18
  .then((headRes) => {
22
19
  if (!headRes.ok) throw new Error("HEAD check failed");
@@ -30,3 +27,5 @@ function fetchBenchmarkData(filename) {
30
27
 
31
28
  return tryFetch();
32
29
  }
30
+
31
+ export { fetchBenchmarkData };
@@ -0,0 +1,2 @@
1
+ window.GITBRANCH = "{{branch | replace('.', '-')}}";
2
+ window.GITSHA = "{{sha}}";