diff --git a/.gitignore b/.gitignore index 4ce3ebf35d58..621132f18ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ debug_container.log *.synctex.gz /x/genutil/config/priv_validator_key.json /x/genutil/data/priv_validator_state.json +/.envrc +/.env diff --git a/.python-version b/.python-version new file mode 100644 index 000000000000..24ee5b1be996 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/Makefile b/Makefile index 1fcd9a12cab2..dbb4f3c12d1c 100644 --- a/Makefile +++ b/Makefile @@ -492,13 +492,17 @@ localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes .PHONY: localnet-start localnet-stop localnet-debug localnet-build-env localnet-build-dlv localnet-build-nodes -test-system: build-v53 build +build-system-test-current: build + mkdir -p ./tests/systemtests/binaries/ + cp $(BUILDDIR)/simd ./tests/systemtests/binaries/ + +test-system: build-v53 build-system-test-current mkdir -p ./tests/systemtests/binaries/ cp $(BUILDDIR)/simd ./tests/systemtests/binaries/ mkdir -p ./tests/systemtests/binaries/v0.53 mv $(BUILDDIR)/simdv53 ./tests/systemtests/binaries/v0.53/simd $(MAKE) -C tests/systemtests test -.PHONY: test-system +.PHONY: test-system build-system-test-current # build-v53 checks out the v0.53.x branch, builds the binary, and renames it to simdv53. build-v53: diff --git a/analysis/__init__.py b/analysis/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/analysis/analysis.ipynb b/analysis/analysis.ipynb new file mode 100644 index 000000000000..d5e03cd12506 --- /dev/null +++ b/analysis/analysis.ipynb @@ -0,0 +1,1934 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-11-05T21:35:56.923025Z", + "start_time": "2025-11-05T21:35:56.920193Z" + } + }, + "source": [ + "from analysis.read_otel import load_otel_runs\n", + "from analysis.analysis import block_summary, plot_block_durations\n", + "import pandas as pd" + ], + "outputs": [], + "execution_count": 6 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-05T21:35:58.646050Z", + "start_time": "2025-11-05T21:35:56.937299Z" + } + }, + "cell_type": "code", + "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims2\")", + "id": "1d7a28bf36057a9d", + "outputs": [], + "execution_count": 7 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-05T21:36:03.528568Z", + "start_time": "2025-11-05T21:35:58.656243Z" + } + }, + "cell_type": "code", + "source": [ + "summary_data = []\n", + "for name, run in runs.items():\n", + " summary = block_summary(run)\n", + " summary_data.append({\n", + " 'run': name,\n", + " 'duration': summary.total_duration_seconds,\n", + " 'blocks': summary.block_count,\n", + " 'avg_block_time': summary.total_duration_seconds / summary.block_count,\n", + " })\n", + "pd.DataFrame(summary_data).set_index('run')\n" + ], + "id": "7eee7d20b6a3ddfc", + "outputs": [ + { + "data": { + "text/plain": [ + " duration blocks avg_block_time\n", + "run \n", + "iavl1-2 1694.763556 1000 1.694764\n", + "iavl1 597.953485 482 1.240567\n", + "iavlx 596.864307 718 0.831287\n", + "iavlx-2 1208.231131 1000 1.208231" + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
durationblocksavg_block_time
run
iavl1-21694.76355610001.694764
iavl1597.9534854821.240567
iavlx596.8643077180.831287
iavlx-21208.23113110001.208231
\n", + "
" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 8 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-05T21:36:07.257996Z", + "start_time": "2025-11-05T21:36:03.574598Z" + } + }, + "cell_type": "code", + "source": "plot_block_durations(runs)", + "id": "f8f479875319ace8", + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "data": [ + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ==" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + } + ], + "layout": { + "template": { + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scattermap": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermap" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Block Duration Comparison" + }, + "xaxis": { + "title": { + "text": "Block Number" + } + }, + "yaxis": { + "title": { + "text": "Duration (ms)" + } + }, + "hovermode": "x unified" + }, + "config": { + "plotlyServerURL": "https://plot.ly" + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 9 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-05T21:36:10.802903Z", + "start_time": "2025-11-05T21:36:07.300201Z" + } + }, + "cell_type": "code", + "source": "plot_block_durations(runs, span_name='Commit')", + "id": "88882244feb963c9", + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "data": [ + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ==" + }, + "y": { + "dtype": "f8", + "bdata": "/Knx0k3SUUAaL90kBrFNQHnpJjEI7ElAAAAAAAAgTEAfhetRuDZZQGIQWDm0YFBAwvUoXI9CVkBg5dAi29lGQAisHFpkl2FA9ihcj8KFUkDHSzeJQfhRQCGwcmiRfVFAGi/dJAZhNUAj2/l+atxXQH0/NV660VZAbxKDwMoRX0AX2c73U1tYQPP91HjpXlZAy6FFtvOdT0CHFtnO9+NMQHoUrkfhAlFAc2iR7XwPT0AfhetRuJ42QPp+arx0i1dAxSCwcmhhVUAlBoGVQyNYQOxRuB6FK1lA8dJNYhAYIkApXI/C9cgyQKjGSzeJoTJAKVyPwvUoMEBQjZduEoMpQOtRuB6FqzJAg8DKoUW2KUAj2/l+avwqQFg5tMh2vitA5KWbxCDwI0CWQ4ts5zsrQKabxCCwcjBA+n5qvHSTMkBg5dAi2zkwQML1KFyPAitApHA9CtcjMEDo+6nx0s0pQFpkO99PzSZAZmZmZmZmHkD+1HjpJlExQO58PzVeOh5AEVg5tMh2OUCNl24Sg4AlQP7UeOkmsRxAokW28/3UGECcxCCwcugVQLOd76fGyxdAiUFg5dCiFkC+nxov3SQYQFpkO99PDRhABoGVQ4tsGECVQ4ts53sYQFTjpZvEoBhA91PjpZvEGEDb+X5qvPQcQHNoke18PxdAHVpkO9/PGUDLoUW28/0bQNEi2/l+ah9AiUFg5dAiH0BzaJHtfD8eQCLb+X5qnDVA001iEFi5HEDtfD81XroaQFpkO99PDRlADAIrhxZZGUBDi2zn+6kZQCcxCKwcWhhAxks3iUFgGEDJdr6fGq8WQKabxCCwchdAQ4ts5/upGEBqvHSTGIQYQLpJDAIrhxdASgwCK4eWF0BfukkMAks7QPp+arx00yNA2/l+ary0IEDdJAaBlcMeQLKd76fGyx1A8KfGSzcJIECJQWDl0CIfQMHKoUW2cxtAyXa+nxovH0A5tMh2vp8bQNEi2/l+6hxAYOXQItv5GkC28/3UeOkaQHNoke18Px5A0SLb+X7qGkBvEoPAyiEbQFTjpZvEIBtALbKd76fGG0Dl0CLb+X4aQPp+arx0kxxAmZmZmZkZH0Bg5dAi23kdQMzMzMzM9FtAxks3iUFYXEBvEoPAyrFcQO18PzVeFm5AbhKDwMrpXUAzMzMzM4tbQJZDi2znq0ZAnMQgsHIoQUDZzvdT4+VYQPyp8dJNQklAsp3vp8ZLakBvEoPAykE/QF66SQwCU1lAAAAAAACQWEDVeOkmMQBXQDEIrBxavFRAJjEIrBwKWEAzMzMzMxNkQKwcWmQ7R2tAnMQgsHLoXUCLbOf7qRlWQKwcWmQ7I2BALbKd76dGPUDJdr6fGo9MQM3MzMzMbFdAi2zn+6nhX0Bg5dAi22lJQKRwPQrXk1FAFK5H4XoUZkBU46WbxNBVQBFYObTI9iFA1XjpJjGIK0A9CtejcL0pQCcxCKwcWiVAJzEIrBzaI0CLbOf7qbErQIlBYOXQYidA2c73U+PlLEA830+Nly4qQEJg5dAiWypAtch2vp86TUAj2/l+auhkQARWDi2y3TtAXrpJDAKbZEBFtvP91DhiQAaBlUOLfGBAvHSTGAQqYkBqvHSTGJxrQBBYObTINkBAu0kMAivHWEDVeOkmMehlQNEi2/l+6l1AexSuR+H6XkDZzvdT40VbQC/dJAaBnV5AQDVeukk8WUA/NV66SdxjQLXIdr6fslxAlUOLbOdDYUCiRbbz/fBhQEFg5dAimyNAQDVeuklMIUBeukkMAusgQAIrhxbZjiRADi2yne+nLkBcj8L1KJwkQNEi2/l+qi5ArkfhehSuL0BeukkMAisyQF66SQwCKyxATDeJQWAlI0CXbhKDwNpCQKabxCCwUjtAaJHtfD+1J0DTTWIQWLkqQBxaZDvfjypAPgrXo3B9KECUGARWDm0mQA8tsp3vpyVAarx0kxiEJ0Coxks3icEkQCUGgZVDiyNAKVyPwvVoKUDXo3A9ClcdQEa28/3U+BxABFYOLbKdH0DC9Shcj0IeQD81XrpJTCBAO99PjZeuIUB9PzVeuskeQKjGSzeJAUFAMgisHFqkMUDfT42XblIpQObQItv5viVA/Knx0k0iJUDO91PjpZshQOSlm8Qg8CBAL90kBoEVIkBBYOXQIpsgQDvfT42XLiFAeekmMQisIUBANV66SQwhQMdLN4lBoCNAH4XrUbieIEBnZmZmZqYgQPLSTWIQGCFAvHSTGASWIECuR+F6FG4iQLbz/dR46SBATmIQWDk0IUDn+6nx0g0hQEOLbOf7qSFARIts5/upIEDwp8ZLN4kiQOxRuB6FayBA3SQGgZVDIkDEILByaFEgQHsUrkfhOiFAmpmZmZnZIEBpke18P3UhQOtRuB6FayJAhetRuB7FIkA5tMh2vt8iQPYoXI/CdSFA1XjpJjHIIEDXo3A9CpcgQCPb+X5qfCBAjZduEoOAIUBYObTIdj4gQOF6FK5HoSBAzvdT46VbIUDByqFFtnMgQIXrUbgehSJAO99PjZduIUD6fmq8dNMhQP7UeOkmsSBALbKd76eGIECgGi/dJMYhQB1aZDvfjyBADi2yne+nN0CHFtnO9zMwQDMzMzMzEzVAUrgehevRKUCe76fGS/cmQDEIrBxapCRA+n5qvHSTI0DD9ShcjwIlQKrx0k1ikCJAL90kBoFVI0BBYOXQIhskQC/dJAaBFSRAQ4ts5/upJECsHFpkO18jQJ3vp8ZLdyVANV66SQxCJUCMbOf7qbElQIGVQ4tsZyNAXrpJDAIrJUBH4XoUrkcjQBWuR+F6FCVAwcqhRbazJEDTTWIQWDklQPT91HjpJiRAi2zn+6mxIkAehetRuN4jQJhuEoPASiRA+n5qvHSTIkAgsHJoke0kQIPAyqFFtiNAuR6F61H4IkBQjZduEkMkQIcW2c730yNA9Shcj8I1JEBwPQrXo/AjQIts5/upsSNAVg4tsp3vI0AIrBxaZHsjQIGVQ4tsZyVATDeJQWBlIkCVQ4ts53slQAisHFpkeyNAqvHSTWLQIkBzaJHtfH8jQFYOLbKdryNAR+F6FK5HI0ArhxbZzvchQF66SQwCKyBA4XoUrkfhIECMbOf7qfEhQKWbxCCwsiFAbef7qfESIECoxks3iUEgQH0/NV66yR5AUI2XbhJDIEDo+6nx0k0eQI/C9ShcjyBAAyuHFtnOH0AkBoGVQwsfQNv5fmq89B5AL90kBoFVIEArhxbZznceQDQzMzMzsx5A/Knx0k3iHkCiRbbz/RQgQJ3vp8ZLtxxAoUW28/3UHkCe76fGSzcfQH0/NV66SR1AtMh2vp8aIEBYObTIdj4gQC/dJAaBFR5ARIts5/spH0B7FK5H4TogQLByaJHt/BxArkfhehSuHEClm8QgsHIfQCcxCKwc2h1Atch2vp8aHUCzne+nxksgQOj7qfHSzR1A/tR46SaxHUCoxks3iQEgQOkmMQisnB1A5dAi2/n+HUDP91PjpZsyQA4tsp3vJyhAKVyPwvXoJkA9CtejcL0lQBkEVg4tsiNANV66SQwCI0AEVg4tst0hQKwcWmQ7nyNAaJHtfD/1JkD+1HjpJrEjQF66SQwCKyVA46WbxCAwJkCmm8QgsPInQEJg5dAi2yZAjGzn+6lxJUAbL90kBoEiQL+fGi/d5CJAWDm0yHY+JUAv3SQGgdUjQFYOLbKdryNA61G4HoVrIkAlBoGVQ8shQDVeukkMAiNAUI2XbhJDI0ATg8DKocUiQAAAAAAAwClAmG4Sg8CKJEB1kxgEVg4nQPHSTWIQ2CVAlUOLbOf7JECBlUOLbKclQH0/NV66ySRAEVg5tMg2IkD7qfHSTQI/QD81XrpJrDNAObTIdr7fLkDn+6nx0o0lQOj7qfHSzSVAK4cW2c53JEByaJHtfP8kQJzEILByKCdAa7x0kxiEJUA9CtejcD0jQFpkO99PDSRA4XoUrkfhKEBKDAIrh9YlQBfZzvdT4yVAd76fGi/dJUC6SQwCK8clQDiJQWDlECVAEFg5tMg2JEASg8DKoUUkQN0kBoGVozFA76fGSzcZXECUGARWDi1hQFCNl24Sk1JA5/up8dJtNED6fmq8dDNhQJMYBFYOnWBAtMh2vp+6ZEBSuB6F63FMQMHKoUW2B2lAKVyPwvVEakCq8dJNYrAyQMuhRbbz5WBAGARWDi1WY0CF61G4Ht1XQCPb+X5qjEtAZmZmZmZqYEAQWDm0yLZoQObQItv5MmNAokW28/2MZUArhxbZzlNjQKFFtvP9HGNA30+Nl25SQUD0/dR46UY0QDQzMzMz219AkxgEVg69XkDRItv5fuZiQL6fGi/dhGhAXrpJDAKDUECsHFpkOz9PQCuHFtnOt2BAukkMAisXXUDJdr6fGj9tQL10kxgELmVAf2q8dJPYOkBaZDvfTyVeQGIQWDm0YGNA1XjpJjFoQUDIdr6fGs9fQIXrUbgehU9AzczMzMwAaEBPjZduEsNjQMuhRbbzoW1AmG4Sg8BiV0A0MzMzM2tWQIlBYOXQqm1AxSCwcmhdaEDXo3A9CudnQEa28/3UZGJAy6FFtvNVWECEwMqhRfJuQJMYBFYOhVxAd76fGi/lXEDTTWIQWJFpQM3MzMzMLEdAzczMzMz8RUB9PzVeuukyQA4tsp3vZ0JAgZVDi2wnNUDIdr6fGm82QJqZmZmZWSxAyHa+nxpvMkAMAiuHFpktQO18PzVemjdADi2yne8nN0BH4XoUrkcrQOOlm8QgUDpAyHa+nxp/QkBSuB6F67E7QH9qvHST+DlAGi/dJAYhNUDLoUW2830sQF66SQwCazZAObTIdr6fLkBWDi2yne8yQPLSTWIQmCpADi2yne9nJkDdJAaBlYMlQF+6SQwCayNA8tJNYhDYJEBoke18P3UmQA4tsp3vZydA5tAi2/m+JEDVeOkmMYglQPp+arx00yFAItv5fmq8IUC0yHa+nxojQLByaJHtPCJAyHa+nxpvIkBMN4lBYGUiQDq0yHa+nyFAa7x0kxiEIkA1XrpJDEIjQH9qvHSTGCNAukkMAisHI0CcxCCwcqgiQGiR7Xw/9SJA6SYxCKxcIUCq8dJNYgBZQH9qvHSTKEJArBxaZDtbY0DrUbgehSdkQA==" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + } + ], + "layout": { + "template": { + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scattermap": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermap" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Commit Duration Comparison" + }, + "xaxis": { + "title": { + "text": "Block Number" + } + }, + "yaxis": { + "title": { + "text": "Duration (ms)" + } + }, + "hovermode": "x unified" + }, + "config": { + "plotlyServerURL": "https://plot.ly" + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 10 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-05T21:36:10.820435Z", + "start_time": "2025-11-05T21:36:10.818144Z" + } + }, + "cell_type": "code", + "source": "", + "id": "8922bfaae8e7ed45", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/analysis/analysis.py b/analysis/analysis.py new file mode 100644 index 000000000000..9d8bf55da472 --- /dev/null +++ b/analysis/analysis.py @@ -0,0 +1,124 @@ +"""Analysis functions for OpenTelemetry trace and metrics data.""" + +import duckdb +from datetime import datetime +from pydantic import BaseModel +import pandas as pd +import plotly.graph_objects as go +from typing import Dict + +# Type alias for runs dictionary (run_name -> DuckDB connection) +Runs = Dict[str, duckdb.DuckDBPyConnection] + + +class BlockSummary(BaseModel): + """Summary of block execution.""" + start_time: datetime + end_time: datetime + total_duration_seconds: float + block_count: int + + +def block_summary(con: duckdb.DuckDBPyConnection) -> BlockSummary: + """ + Generate a high-level summary of block execution. + + Args: + con: DuckDB connection with spans view loaded + + Returns: + BlockSummary object with: + - start_time: Start time of first block + - end_time: End time of last block + - total_duration_seconds: Total duration from first block start to last block end + - block_count: Total number of blocks processed + + Example: + >>> from analysis.read_otel import load_otel_data + >>> con = load_otel_data('/path/to/data') + >>> summary = block_summary(con) + >>> print(f"Processed {summary.block_count} blocks in {summary.total_duration_seconds:.2f}s") + """ + result = con.sql(""" + SELECT + MIN(start_time) AS start_time, + MAX(end_time) AS end_time, + EXTRACT(EPOCH FROM (MAX(end_time) - MIN(start_time))) AS total_duration_seconds, + COUNT(*) AS block_count + FROM spans + WHERE span_name = 'Block' AND scope = 'cosmos-sdk/baseapp' + """).fetchone() + + return BlockSummary( + start_time=result[0], + end_time=result[1], + total_duration_seconds=result[2], + block_count=result[3], + ) + + +def block_durations(con: duckdb.DuckDBPyConnection, span_name: str = 'Block') -> pd.DataFrame: + """ + Get duration for each block. + + Args: + con: DuckDB connection with spans view loaded + + Returns: + DataFrame with columns: + - block_number: Sequential block number (1-indexed) + - duration_ms: Block duration in milliseconds + + Example: + >>> from analysis.read_otel import load_otel_data + >>> con = load_otel_data('/path/to/data') + >>> df = block_durations(con) + >>> df.head() + """ + return con.sql(""" + SELECT + ROW_NUMBER() OVER (ORDER BY start_time) AS block_number, + EXTRACT(EPOCH FROM duration) * 1000 AS duration_ms + FROM spans + WHERE span_name = ? AND scope = 'cosmos-sdk/baseapp' + ORDER BY start_time + """, params=[span_name]).df() + + +def plot_block_durations(runs: Runs, span_name: str = 'Block') -> go.Figure: + """ + Create a plotly line chart comparing block durations across runs. + + Args: + runs: Dictionary mapping run name to DuckDB connection + + Returns: + Plotly Figure object with block duration traces for each run + + Example: + >>> from analysis.read_otel import load_otel_runs + >>> runs = load_otel_runs('/path/to/data') + >>> fig = plot_block_durations(runs) + >>> fig.show() + """ + fig = go.Figure() + + for run_name, con in runs.items(): + df = block_durations(con, span_name) + fig.add_trace(go.Scatter( + x=df['block_number'], + y=df['duration_ms'], + mode='lines', + name=run_name, + line=dict(width=2) + )) + + fig.update_layout( + title=f'{span_name} Duration Comparison', + xaxis_title='Block Number', + yaxis_title='Duration (ms)', + hovermode='x unified', + template='plotly_white' + ) + + return fig \ No newline at end of file diff --git a/analysis/read_otel.py b/analysis/read_otel.py new file mode 100644 index 000000000000..179e2c3e9cd7 --- /dev/null +++ b/analysis/read_otel.py @@ -0,0 +1,128 @@ +"""Load OpenTelemetry JSONL data into DuckDB views.""" + +import duckdb +from pathlib import Path +from typing import Dict + + +def load_otel_data(data_path: str | Path) -> duckdb.DuckDBPyConnection: + """ + Load OpenTelemetry data from a directory containing trace.jsonl, logs.jsonl, and metrics.jsonl. + + Creates three views in the returned DuckDB connection: + - spans: flattened trace data + - logs: flattened log data + - metrics: flattened metrics data + + Args: + data_path: Path to directory containing the JSONL files + + Returns: + DuckDB connection with views created + + Example: + >>> con = load_otel_data('/path/to/data') + >>> con.sql("SELECT span_name, count(*) FROM spans GROUP BY span_name").show() + """ + data_path = Path(data_path) + con = duckdb.connect(':memory:') + + # Create spans view from trace.jsonl + trace_file = data_path / 'trace.jsonl' + if trace_file.exists(): + con.execute(f""" + CREATE VIEW spans AS + SELECT + Name AS span_name, + SpanContext.TraceID AS trace_id, + SpanContext.SpanID AS span_id, + Parent.SpanID AS parent_span_id, + CAST(StartTime AS TIMESTAMPTZ) AS start_time, + CAST(EndTime AS TIMESTAMPTZ) AS end_time, + CAST(EndTime AS TIMESTAMPTZ) - CAST(StartTime AS TIMESTAMPTZ) AS duration, + InstrumentationScope.Name AS scope, + ChildSpanCount AS child_span_count, + Attributes, + Resource, + Status + FROM read_ndjson_auto('{trace_file}') + """) + + # Create logs view from logs.jsonl + logs_file = data_path / 'logs.jsonl' + if logs_file.exists(): + con.execute(f""" + CREATE VIEW logs AS + SELECT + CAST(Timestamp AS TIMESTAMPTZ) AS timestamp, + CAST(ObservedTimestamp AS TIMESTAMPTZ) AS observed_timestamp, + Severity AS severity, + SeverityText AS severity_text, + Body.Value AS body, + TraceID AS trace_id, + SpanID AS span_id, + Attributes, + Resource, + Scope + FROM read_ndjson_auto('{logs_file}') + """) + + # Create metrics view from metrics.jsonl (if file exists and has data) + metrics_file = data_path / 'metrics.jsonl' + if metrics_file.exists() and metrics_file.stat().st_size > 0: + # Flatten the deeply nested metrics structure + # Structure: Resource → ScopeMetrics[] → Metrics[] → Data → DataPoints[] + con.execute(f""" + CREATE VIEW metrics AS + SELECT + scope_metric.Scope.Name AS scope_name, + scope_metric.Scope.Version AS scope_version, + metric.Name AS metric_name, + metric.Description AS metric_description, + metric.Unit AS unit, + metric.Data.Temporality AS temporality, + metric.Data.IsMonotonic AS is_monotonic, + CAST(dp.Time AS TIMESTAMPTZ) AS time, + CAST(dp.StartTime AS TIMESTAMPTZ) AS start_time, + dp.Value AS value, + dp.Count AS count, + dp.Sum AS sum, + dp.Min AS min, + dp.Max AS max, + dp.Attributes AS attributes, + raw.Resource AS resource + FROM read_ndjson_auto('{metrics_file}') AS raw + CROSS JOIN UNNEST(raw.ScopeMetrics) AS t(scope_metric) + CROSS JOIN UNNEST(scope_metric.Metrics) AS t2(metric) + CROSS JOIN UNNEST(metric.Data.DataPoints) AS t3(dp) + """) + + return con + + +def load_otel_runs(runs_dir: str | Path) -> Dict[str, duckdb.DuckDBPyConnection]: + """ + Load OpenTelemetry data from multiple run directories. + + Args: + runs_dir: Path to directory containing subdirectories with OTEL data + Each subdirectory should contain trace.jsonl, logs.jsonl, metrics.jsonl + + Returns: + Dictionary mapping run name (subdirectory name) to DuckDB connection + + Example: + >>> connections = load_otel_runs('/Users/arc/iavl-bench-data/sims') + >>> # Returns: {'iavlx': , 'iavl1': } + >>> connections['iavlx'].sql("SELECT count(*) FROM spans").show() + """ + runs_dir = Path(runs_dir) + connections = {} + + for subdir in runs_dir.iterdir(): + if subdir.is_dir(): + # Check if it has trace.jsonl to confirm it's a valid run directory + if (subdir / 'trace.jsonl').exists(): + connections[subdir.name] = load_otel_data(subdir) + + return connections \ No newline at end of file diff --git a/baseapp/abci.go b/baseapp/abci.go index f7ede1c840f6..c0ff059017d7 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -106,10 +106,13 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha // add block gas meter for any genesis transactions (allow infinite gas) finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter())) - res, err := app.abciHandlers.InitChainer(finalizeState.Context(), req) + ctx := finalizeState.Context() + ctx, span := ctx.StartSpan(tracer, "InitChain") + res, err := app.abciHandlers.InitChainer(ctx, req) if err != nil { return nil, err } + span.End() if len(req.Validators) > 0 { if len(req.Validators) != len(res.Validators) { @@ -152,7 +155,10 @@ func (app *BaseApp) Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) { // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. -func (app *BaseApp) Query(_ context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) { +func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) { + ctx, span := tracer.Start(ctx, "Query") + defer span.End() + // add panic recovery for all queries // // Ref: https://github.com/cosmos/cosmos-sdk/pull/8039 @@ -342,6 +348,9 @@ func (app *BaseApp) ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*ab // will contain relevant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain the relevant gas execution context. func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { + _, span := tracer.Start(context.Background(), "CheckTx") + defer span.End() + var mode sdk.ExecMode switch req.Type { @@ -454,7 +463,10 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc } }() - resp, err = app.abciHandlers.PrepareProposalHandler(prepareProposalState.Context(), req) + ctx := prepareProposalState.Context() + ctx, span := ctx.StartSpan(tracer, "PrepareProposal") + defer span.End() + resp, err = app.abciHandlers.PrepareProposalHandler(ctx, req) if err != nil { app.logger.Error("failed to prepare proposal", "height", req.Height, "time", req.Time, "err", err) return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil @@ -513,7 +525,10 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc } processProposalState := app.stateManager.GetState(execModeProcessProposal) - processProposalState.SetContext(app.getContextForProposal(processProposalState.Context(), req.Height). + ctx := processProposalState.Context() + ctx, span := ctx.StartSpan(tracer, "ProcessProposal") + defer span.End() + processProposalState.SetContext(app.getContextForProposal(ctx, req.Height). WithVoteInfos(req.ProposedLastCommit.Votes). // this is a set of votes that are not finalized yet, wait for commit WithBlockHeight(req.Height). WithBlockTime(req.Time). @@ -595,6 +610,9 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) ( return nil, errors.New("application ExtendVote handler not set") } + ctx, span := ctx.StartSpan(tracer, "ExtendVote") + defer span.End() + // If vote extensions are not enabled, as a safety precaution, we return an // error. cp := app.GetConsensusParams(ctx) @@ -666,6 +684,9 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r ctx = sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager) } + ctx, span := ctx.StartSpan(tracer, "VerifyVoteExtension") + defer span.End() + // If vote extensions are not enabled, as a safety precaution, we return an // error. cp := app.GetConsensusParams(ctx) @@ -716,7 +737,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r // Execution flow or by the FinalizeBlock ABCI method. The context received is // only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context() // must be used. -func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { +func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { var events []abci.Event if err := app.checkHalt(req.Height, req.Time); err != nil { @@ -750,7 +771,6 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager) finalizeState = app.stateManager.GetState(execModeFinalize) } - // Context is now updated with Header information. finalizeState.SetContext(finalizeState.Context(). WithBlockHeader(header). @@ -782,6 +802,16 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithHeaderHash(req.Hash)) } + // instrument FinalizeBlock here and capture block context, because we'll switch back to that after FinalizeBlock + blockCtx := finalizeState.Context() + ctx, span := blockCtx.StartSpan(tracer, "FinalizeBlock") + finalizeState.SetContext(ctx) + defer span.End() + defer func() { + // restore the go context to block context so that Commit is not instrumented as a child of FinalizeBlock + finalizeState.SetContext(finalizeState.Context().WithContext(blockCtx.Context())) + }() + preblockEvents, err := app.preBlock(req) if err != nil { return nil, err @@ -796,9 +826,11 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request // First check for an abort signal after beginBlock, as it's the first place // we spend any significant amount of time. + // NOTE: we use the go context here because that is used for cancellation + // whereas the finalize state context is used for block execution select { - case <-ctx.Done(): - return nil, ctx.Err() + case <-goCtx.Done(): + return nil, goCtx.Err() default: // continue } @@ -846,15 +878,17 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithBlockGasUsed(blockGasUsed). WithBlockGasWanted(blockGasWanted), ) - endBlock, err := app.endBlock(finalizeState.Context()) + endBlock, err := app.endBlock() if err != nil { return nil, err } // check after endBlock if we should abort, to avoid propagating the result + // NOTE: we use the go context here because that is used for cancellation + // whereas the finalize state context is used for block execution select { - case <-ctx.Done(): - return nil, ctx.Err() + case <-goCtx.Done(): + return nil, goCtx.Err() default: // continue } @@ -959,7 +993,11 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error { // height. func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { finalizeState := app.stateManager.GetState(execModeFinalize) - header := finalizeState.Context().BlockHeader() + ctx := finalizeState.Context() + ctx, span := ctx.StartSpan(tracer, "Commit") + defer span.End() + + header := ctx.BlockHeader() retainHeight := app.GetBlockRetentionHeight(header.Height) if app.abciHandlers.Precommiter != nil { @@ -1005,6 +1043,12 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { // The SnapshotIfApplicable method will create the snapshot by starting the goroutine app.snapshotManager.SnapshotIfApplicable(header.Height) + // track metrics and setup span for next block + // TODO: should this be in the state manager instead? + blockCnt.Add(ctx, 1) + blockTime.Record(ctx, time.Since(app.blockStartTime).Seconds()) + app.blockStartTime = time.Now() + return resp, nil } diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 31fd20b1f7e7..2d0362b467f5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,13 +1,16 @@ package baseapp +// need to import telemetry before anything else for side effects +import _ "github.com/cosmos/cosmos-sdk/telemetry" + import ( - "context" "fmt" "maps" "math" "slices" "strconv" "sync" + "time" "github.com/cockroachdb/errors" abci "github.com/cometbft/cometbft/abci/types" @@ -15,15 +18,20 @@ import ( cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/gogoproto/proto" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" protov2 "google.golang.org/protobuf/proto" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/log" "cosmossdk.io/store" storemetrics "cosmossdk.io/store/metrics" "cosmossdk.io/store/snapshots" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp/config" "github.com/cosmos/cosmos-sdk/baseapp/oe" "github.com/cosmos/cosmos-sdk/baseapp/state" @@ -59,6 +67,41 @@ const ( var _ servertypes.ABCI = (*BaseApp)(nil) +var ( + tracer = otel.Tracer("cosmos-sdk/baseapp") + meter = otel.Meter("cosmos-sdk/baseapp") + blockCnt metric.Int64Counter + txCnt metric.Int64Counter + blockTime metric.Float64Histogram + txTime metric.Int64Histogram +) + +func init() { + var err error + blockCnt, err = meter.Int64Counter("block.count") + if err != nil { + panic(err) + } + txCnt, err = meter.Int64Counter("tx.count") + if err != nil { + panic(err) + } + blockTime, err = meter.Float64Histogram("block.time", + metric.WithUnit("s"), + metric.WithDescription("Block time in seconds"), + ) + if err != nil { + panic(err) + } + txTime, err = meter.Int64Histogram("tx.time", + metric.WithUnit("us"), + metric.WithDescription("Transaction time in microseconds"), + ) + if err != nil { + panic(err) + } +} + // BaseApp reflects the ABCI application implementation. type BaseApp struct { // initialized on creation @@ -164,6 +207,8 @@ type BaseApp struct { // Optional alternative tx runner, used for block-stm parallel transaction execution. If nil, default txRunner is used. txRunner sdk.TxRunner + + blockStartTime time.Time } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a @@ -184,8 +229,11 @@ func NewBaseApp( fauxMerkleMode: false, sigverifyTx: true, gasConfig: config.GasConfig{QueryGasLimit: math.MaxUint64}, + blockStartTime: time.Now(), } + // initialize tracer + for _, option := range options { option(app) } @@ -656,6 +704,8 @@ func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, erro if app.abciHandlers.PreBlocker != nil { finalizeState := app.stateManager.GetState(execModeFinalize) ctx := finalizeState.Context().WithEventManager(sdk.NewEventManager()) + ctx, span := ctx.StartSpan(tracer, "preBlock") + defer span.End() rsp, err := app.abciHandlers.PreBlocker(ctx, req) if err != nil { return nil, err @@ -681,7 +731,10 @@ func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, er ) if app.abciHandlers.BeginBlocker != nil { - resp, err = app.abciHandlers.BeginBlocker(app.stateManager.GetState(execModeFinalize).Context()) + ctx := app.stateManager.GetState(execModeFinalize).Context() + ctx, span := ctx.StartSpan(tracer, "beginBlock") + defer span.End() + resp, err = app.abciHandlers.BeginBlocker(ctx) if err != nil { return resp, err } @@ -739,11 +792,14 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI // endBlock is an application-defined function that is called after transactions // have been processed in FinalizeBlock. -func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { +func (app *BaseApp) endBlock() (sdk.EndBlock, error) { var endblock sdk.EndBlock if app.abciHandlers.EndBlocker != nil { - eb, err := app.abciHandlers.EndBlocker(app.stateManager.GetState(execModeFinalize).Context()) + ctx := app.stateManager.GetState(execModeFinalize).Context() + ctx, span := ctx.StartSpan(tracer, "endBlock") + defer span.End() + eb, err := app.abciHandlers.EndBlocker(ctx) if err != nil { return endblock, err } @@ -773,12 +829,16 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { + startTime := time.Now() + ctx := app.getContextForTx(mode, txBytes, txIndex) + ctx, span := ctx.StartSpan(tracer, "runTx") + defer span.End() + // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter, so we initialize upfront. var gasWanted uint64 - ctx := app.getContextForTx(mode, txBytes, txIndex) if incarnationCache != nil { ctx = ctx.WithIncarnationCache(incarnationCache) } @@ -861,7 +921,11 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) + anteCtx, anteSpan := anteCtx.StartSpan(tracer, "anteHandler") newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) + anteSpan.End() + // now we should back to the previous go context which didn't capture the anteHandler instrumentation span + newCtx = newCtx.WithContext(ctx) if !newCtx.IsZero() { // At this point, newCtx.MultiStore() is a store branch, or something else @@ -951,6 +1015,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex consumeBlockGas() msCache.Write() + + txCnt.Add(ctx, 1) + txTime.Record(ctx, time.Since(startTime).Microseconds()) } if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) { @@ -968,6 +1035,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // Handler does not exist for a given message route. Otherwise, a reference to a // Result is returned. The caller must not commit state if an error is returned. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode sdk.ExecMode) (*sdk.Result, error) { + ctx, span := ctx.StartSpan(tracer, "runMsgs") + defer span.End() + events := sdk.EmptyEvents() var msgResponses []*codectypes.Any @@ -977,15 +1047,26 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me break } - ctx = ctx.WithMsgIndex(i) + msgCtx := ctx.WithMsgIndex(i) handler := app.msgServiceRouter.Handler(msg) if handler == nil { return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } + msgTypeUrl := sdk.MsgTypeURL(msg) + // we create two spans here for easy visualization in trace logs, this can be removed later if deemed unnecessary + msgCtx, msgSpan := msgCtx.StartSpan(tracer, "msgHandler", + trace.WithAttributes( + attribute.String("msg_type", msgTypeUrl), + attribute.Int("msg_index", i), + ), + ) + msgCtx, msgSpan2 := msgCtx.StartSpan(tracer, fmt.Sprintf("msgHandler.%s", msgTypeUrl)) // ADR 031 request type routing msgResult, err := handler(ctx, msg) + msgSpan2.End() + msgSpan.End() if err != nil { return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i) } @@ -1019,7 +1100,6 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me } msgResponses = append(msgResponses, msgResponse) } - } data, err := makeABCIData(msgResponses) diff --git a/baseapp/state/manager.go b/baseapp/state/manager.go index fb8cb55e730f..e17b55547310 100644 --- a/baseapp/state/manager.go +++ b/baseapp/state/manager.go @@ -5,6 +5,9 @@ import ( "sync" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "go.opentelemetry.io/otel" + otelattr "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/header" "cosmossdk.io/log" @@ -14,6 +17,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var ( + tracer = otel.Tracer("cosmos-sdk/baseapp") +) + type Manager struct { // volatile states: // @@ -107,6 +114,17 @@ func (mgr *Manager) SetState( mgr.processProposalState = baseState case sdk.ExecModeFinalize: + // add tracing span instrumentation here when the context is initialized for the block + height := h.Height + if height == 0 { + height = 1 + } + baseState.ctx, baseState.span = baseState.ctx.StartSpan(tracer, "Block", + trace.WithAttributes( + otelattr.Int64("height", height), + otelattr.Int64("time_unix_nano", h.Time.UnixNano()), + ), + ) mgr.finalizeBlockState = baseState default: @@ -129,6 +147,10 @@ func (mgr *Manager) ClearState(mode sdk.ExecMode) { mgr.processProposalState = nil case sdk.ExecModeFinalize: + // complete tracing span instrumentation here when the context is cleared for the block + if mgr.finalizeBlockState != nil { + mgr.finalizeBlockState.span.End() + } mgr.finalizeBlockState = nil default: diff --git a/baseapp/state/state.go b/baseapp/state/state.go index fedfe5b1fd62..7655e8f83c12 100644 --- a/baseapp/state/state.go +++ b/baseapp/state/state.go @@ -4,6 +4,7 @@ import ( "sync" storetypes "cosmossdk.io/store/types" + "go.opentelemetry.io/otel/trace" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,6 +14,8 @@ type State struct { mtx sync.RWMutex ctx sdk.Context + + span trace.Span } func NewState(ctx sdk.Context, ms storetypes.CacheMultiStore) *State { diff --git a/client/v2/go.mod b/client/v2/go.mod index e37d6f682dad..b27bfcba5b27 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -30,7 +30,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -38,6 +37,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -63,6 +63,8 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -73,6 +75,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -83,11 +86,14 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -108,6 +114,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -120,16 +127,19 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -139,15 +149,38 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 28cd742566dc..bcc7db499dfd 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -89,6 +88,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -199,6 +200,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -253,6 +257,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -346,6 +353,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -354,6 +363,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -456,6 +467,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -572,6 +585,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -598,6 +613,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -605,8 +622,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -631,6 +648,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -688,6 +707,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -699,6 +722,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -714,17 +739,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -837,6 +900,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -853,6 +917,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -872,6 +937,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/go.mod b/go.mod index af11e9186b28..211df225b4cc 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,10 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 github.com/cosmos/gogoproto v1.7.2 + github.com/cosmos/iavl v1.2.6 github.com/cosmos/ledger-cosmos-go v0.16.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/edsrzf/mmap-go v1.0.0 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.7.0 github.com/google/gofuzz v1.2.0 @@ -46,8 +48,6 @@ require ( github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-isatty v0.0.20 github.com/mdp/qrterminal/v3 v3.2.1 - github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.67.3 github.com/rs/zerolog v1.34.0 github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.1 @@ -57,8 +57,24 @@ require ( github.com/tendermint/go-amino v0.16.0 github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.8.1 + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 + go.opentelemetry.io/contrib/otelconf v0.18.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 + go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.44.0 + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 golang.org/x/sync v0.18.0 google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 google.golang.org/grpc v1.77.0 @@ -80,7 +96,6 @@ require ( cosmossdk.io/schema v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -110,6 +125,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect @@ -119,7 +135,6 @@ require ( github.com/cockroachdb/redact v1.1.6 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect github.com/cometbft/cometbft-db v0.14.1 // indirect - github.com/cosmos/iavl v1.2.6 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -128,6 +143,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -141,6 +157,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -154,6 +171,8 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -171,6 +190,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -184,21 +204,29 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.16 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect @@ -207,17 +235,19 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.21.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sys v0.38.0 // indirect diff --git a/go.sum b/go.sum index b9b97fa75a76..44144084e8a2 100644 --- a/go.sum +++ b/go.sum @@ -51,7 +51,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -159,6 +158,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -274,6 +275,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -338,6 +342,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -439,6 +446,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -447,6 +456,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -559,6 +570,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -678,6 +691,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -704,6 +719,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -711,8 +728,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -737,6 +754,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -801,6 +820,10 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -814,6 +837,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -829,25 +854,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -975,6 +1036,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -992,6 +1054,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1011,6 +1074,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/iavlx/README.md b/iavlx/README.md new file mode 100644 index 000000000000..5905be10f32c --- /dev/null +++ b/iavlx/README.md @@ -0,0 +1,147 @@ +# iavl + +## Code Organization + +### Node Types, Memory & Disk Layouts + +Much of this code was influenced by memiavl and sometimes even copied directly from it. +The `NodeID` design is mainly from iavl/v2. +The `NodePointer` design introduces the possibility of doing node eviction similar to iavl/v2, +but with non-blocking thread safety using `atomic.Pointer` so that eviction can happen in the background without +blocking reads or writes. + +* `node.go`: the `Node` interface which all 3 node types implement (`MemNode`, `BranchPersisted`, `LeafPersisted`) +* `mem_node.go`: in-memory node structure, new nodes always use the `MemNode` type +* `node_pointer.go`: all child references are wrapped in `NodePointer` which can point to either an in-memory node or an + on-disk node, or both (if the node has been written and node evicted) +* `node_id.go`: defines `NodeID` (version + index + leaf) and `NodeRef` (either a `NodeID` or a node offset in the + changeset file) +* `branch_layout.go`: defines the on-disk layout for branch nodes +* `leaf_layout.go`: defines the on-disk layout for leaf nodes +* `branch_persisted.go`: a wrapper around `BranchLayout` which implements the `Node` interface and also tracks a store + reference +* `leaf_persisted.go`: a wrapper around `LeafLayout` which implements the `Node` interface and also tracks a store + reference + +### Tree Management & Updating + +For managing tree state, we define two core types `Tree` and `CommitTree`. +We directly read from and apply updates to `Tree`s but these updates only affect the persistent state of the tree if +they are applied and committed to a `CommitTree`. + +* `tree.go`: a `Tree` struct which implements the Cosmos SDK `KVStore` interface and implements the key methods (get, + set, + delete, commit, etc). `Tree`s can be mutated, and changes can either be committed or discarded. This is essentially an + in-memory reference to a tree at a specific version that could be used read-only or mutated ad hoc without affecting + the underlying persistent tree (say for instance in `CheckTx`). +* `commit_tree.go`: defines the `CommitTree` structure which manages the persistent tree state. Using `CommitTree` you + can + create new mutable `Tree` instance using `Branch` and decide to `Apply` its changes to the persistent tree or discard + them. Calling `Commit` flushes changes to the underlying `TreeStore` which does all of the on disk state management + and cleanup. In `CommitTree` we also have an asynchronous WAL writing process (optional) and maintain a background + eviction process. +* `update.go`: types for batching changes which can later be commited or discarded +* `node_update.go` and : the code for setting and deleting nodes and doing tree rebalancing, adapted from memiavl and + iavl/v1 +* `node_hash.go`: code for computing node hashes, adapted from memiavl and iavl/v1 +* `iterator.go`: implements the Cosmos SDK `Iterator` interface, adapted from memiavl and iavl/v1 + +### Disk State Management + +### Central Coordination + +These files are the central core of managing on-disk state across multiple changesets which may be in the process of +being written or compacted. **This is the most complex part of the codebase.** + +* `tree_store.go`: code for dispatching read operations to the correct changeset, writing commits to new changesets, + and coordinating background compaction and cleanup of old changesets +* `cleanup.go`: the actual background cleanup and compaction thread + +#### Changeset Reading, Writing and Compaction + +* `changeset_files.go`: `ChangesetFiles` represents the five files which make up a changeset: + * `kv.log`: all of the key/value pairs in the changeset, and optionally the write-ahead log for replay (this is + configurable) + * `leaves.dat`: an array of `LeafLayout` structs + * `branches.dat`: an array of `BranchLayout` structs + * `verions.dat`: an array of `VersionInfo` structs, one for each version in the changeset + * `info.dat`: a single `ChangesetInfo` struct which tracks metadata about the changeset including the range of + versions + it contains and the number of orphaned nodes +* `changeset.go`: the `Changeset` struct wraps mmap's of the five changeset files and provides + methods for reading nodes from disk and marking them as orphaned. It includes some complex code for safely disposing + of `Changeset` instances because we need to either 1) reopen the memmap to change its size, or 2) close the + `Changeset` because it has been compacted and will be deleted. This is managed using pinning, a reference count, and + atomic booleans to track eviction (the desire to dispose and delete) and disposal (the actual disposal). +* `changeset_writer.go`: code for iteratively writing changesets to disk node by node in post-order traversal order. + Node references can either be by + `NodeID` or offsets (offsets have been disabled due to some unresolved bugs) +* `compactor.go`: code for rewriting one or more changesets into a new compacted changeset, skipping + orphaned nodes and updating offsets as needed (this offset rewrite code is currently buggy and disabled) + +#### Helpers + +* `version_info.go`: defines the on-disk layout for version info records, which track the root node and other metadata + for + each version +* `changeset_info.go`: defines the on-disk layout for the changeset info record, which tracks metadata + about the entire changeset including version range and number of orphaned nodes +* `kvlog.go`: code for reading key/value pairs from the `kv.log` file +* `kvlog_writer.go`: code for writing key/value pairs to the `kv.log` file, which can be structured as a write-ahead + operation log for replay and crash recovery (reply and recovery aren't implemented yet) +* `mmap.go`: the `MmapFile` mem-map wrapper +* `writer.go`: `FileWriter` and `StructWriter` wrappers for writing raw bytes and structs to files +* `reader.go`: `StructMap` and `NodeMap` wrappers for representing memory-mapped arrays of structs and nodes + +### Multi-tree Management + +* `multi_tree.go`: wraps multiple `Tree`s into a `MultiTree` which provides a mutable way to write a tree without + committing the changes to the persistent tree immediately (can be discarded) +* `commit_multi_tree.go`: wraps multiple `CommitTree`s into a `CommitMultiTree` which provides a way to create mutable + `MultiTree`s and commit their changes to the underlying persistent trees (or discard them). This can eventually + implement `RootMultiStore` and replace the SDK's store package. `CommitMultiTree` makes the optimization of running + `Commit` in parallel across all `CommitTree`s which could improve performance. + +### Options + +Options are mantained by the `Options` struct in `options.go`. Many options have a getter which uses a default value if +the option is not set. + +The main options we're controlling now are: + +* `WriteWAL`: whether we write all updates to the kv-log as a replayable write-ahead log (WAL). If this is enabled we + will fsync the WAL either asynchronously or synchronously (based on the `WalSyncBuffer` option). Enabling WAL could + actually improve performance because we asynchronously write key/value data in advance of `CommitTree.Commit` being + called. +* `EvictDepth`: the depth of the tree beyond which we will evict nodes from memory as soon as they are on disk. This is + the main lever for controlling memory usage. Using more memory could improve performance. +* `RetainVersions`: the number of recent versions to retain when we are compacting. Eventually we also want to enable + some sort of snapshot-based compaction (retaining full trees every N versions). +* `MinCompactionSeconds`: the minimum number of seconds to wait before starting a new compaction run (note that this + currently includes the time it takes to compact). +* `CompactWAL`: whether to compact the WAL when we are compacting changesets. In the future, we can distinguish between + compacting the WAL before our first checkpoint and retaining it after the first checkpoint. +* `ChangesetMaxTarget`: the size of a changeset after which we will roll over to a new changeset for the next version. +* `CompactionMaxTarget`: the target size of a compacted changeset. When adding a new changeset into our compaction will + stay below this number, we will join multiple changesets into a single compacted changeset. +* `CompactionOrphanRatio`: the ratio of orphaned nodes in a changeset beyond which we will trigger it for early + compaction (used together with `CompactionOrphanAge`) +* `CompactionOrphanAge`: the average age of orphaned nodes in a changeset beyond which we will trigger it for early + compaction (used together with `CompactionOrphanRatio`) +* `CompactAfterVersions`: the number of versions after which we will trigger a compaction when any orphans are present, + measured in versions since the last compaction. +* `ReaderUpdateInterval`: when writing multiple versions to a changeset, the number of versions after which we will open + the changeset for reading even if it has not been completed, so that readers can access the latest versions sooner and + flush memory. Set this to a shorter interval if we want to constrain memory usage more tightly and longer if we want + to reduce the number of times memmaps are re-opened for reading. + +### Utilities + +* `dot_graph.go`: code for exporting trees to Graphviz dot graph format for visualization +* `verify.go`: code for verifying tree integrity + +### Tests + +* `tree_test.go`: the only tests we have so far. These do, however, use property-based testing so we are generating + random operation sets, applying them to both iavlx and iavl/v1 trees. At each step, we confirm that behavior is + identical, including verification of hashes and verifying that invariants are maintained. \ No newline at end of file diff --git a/iavlx/branch_layout.go b/iavlx/branch_layout.go new file mode 100644 index 000000000000..4c2c2f0e15fc --- /dev/null +++ b/iavlx/branch_layout.go @@ -0,0 +1,32 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(BranchLayout{}) != SizeBranch { + panic(fmt.Sprintf("invalid BranchLayout size: got %d, want %d", unsafe.Sizeof(BranchLayout{}), SizeBranch)) + } +} + +const ( + SizeBranch = 80 +) + +type BranchLayout struct { + Id NodeID + Left NodeID + Right NodeID + LeftOffset uint32 // absolute offset + RightOffset uint32 // absolute offset + KeyOffset uint32 + Height uint8 + Size uint32 // TODO 5 bytes? (there are 3 bytes of padding here) + Hash [32]byte +} + +func (b BranchLayout) ID() NodeID { + return b.Id +} diff --git a/iavlx/branch_persisted.go b/iavlx/branch_persisted.go new file mode 100644 index 000000000000..c5ec0787af77 --- /dev/null +++ b/iavlx/branch_persisted.go @@ -0,0 +1,118 @@ +package iavlx + +import ( + "bytes" + "sync/atomic" +) + +type BranchPersisted struct { + store *Changeset + layout BranchLayout +} + +func (node *BranchPersisted) Left() *NodePointer { + return &NodePointer{ + mem: atomic.Pointer[MemNode]{}, + store: node.store, + fileIdx: node.layout.LeftOffset, + id: node.layout.Left, + } +} + +func (node *BranchPersisted) Right() *NodePointer { + return &NodePointer{ + mem: atomic.Pointer[MemNode]{}, + store: node.store, + fileIdx: node.layout.RightOffset, + id: node.layout.Right, + } +} + +func (node *BranchPersisted) ID() NodeID { + return node.layout.Id +} + +func (node *BranchPersisted) Height() uint8 { + return node.layout.Height +} + +func (node *BranchPersisted) IsLeaf() bool { + return false +} + +func (node *BranchPersisted) Size() int64 { + return int64(node.layout.Size) +} + +func (node *BranchPersisted) Version() uint32 { + return uint32(node.layout.Id.Version()) +} + +func (node *BranchPersisted) Key() ([]byte, error) { + return node.store.ReadK(node.layout.Id, node.layout.KeyOffset) +} + +func (node *BranchPersisted) Value() ([]byte, error) { + return nil, nil +} + +func (node *BranchPersisted) Hash() []byte { + return node.layout.Hash[:] +} + +func (node *BranchPersisted) SafeHash() []byte { + return node.layout.Hash[:] +} + +func (node *BranchPersisted) MutateBranch(version uint32) (*MemNode, error) { + key, err := node.Key() + if err != nil { + return nil, err + } + + memNode := &MemNode{ + height: node.Height(), + size: node.Size(), + version: version, + key: key, + left: node.Left(), + right: node.Right(), + } + return memNode, err +} + +func (node *BranchPersisted) Get(key []byte) (value []byte, index int64, err error) { + nodeKey, err := node.Key() + if err != nil { + return nil, 0, err + } + + if bytes.Compare(key, nodeKey) < 0 { + leftNode, err := node.Left().Resolve() + if err != nil { + return nil, 0, err + } + + return leftNode.Get(key) + } + + rightNode, err := node.Right().Resolve() + if err != nil { + return nil, 0, err + } + + value, index, err = rightNode.Get(key) + if err != nil { + return nil, 0, err + } + + index += node.Size() - rightNode.Size() + return value, index, nil +} + +func (node *BranchPersisted) String() string { + // TODO implement me + panic("implement me") +} + +var _ Node = (*BranchPersisted)(nil) diff --git a/iavlx/cache_tree.go b/iavlx/cache_tree.go new file mode 100644 index 000000000000..53550442ebaf --- /dev/null +++ b/iavlx/cache_tree.go @@ -0,0 +1,139 @@ +package iavlx + +import ( + io "io" + "sync" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/iavlx/internal" +) + +type CacheTree struct { + mtx sync.Mutex // TODO do we really need a mutex or could this be part of the caller contract? + parent storetypes.KVStore + dirty bool + cache internal.BTree +} + +func NewCacheTree(parent storetypes.KVStore) *CacheTree { + return &CacheTree{parent: parent, cache: internal.NewBTree()} +} + +func (store *CacheTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +func (store *CacheTree) CacheWrap() storetypes.CacheWrap { + return NewCacheTree(store) +} + +func (store *CacheTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO implement tracing + return NewCacheTree(store) +} + +func (store *CacheTree) Get(key []byte) (value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + + storetypes.AssertValidKey(key) + + var ok bool + value, ok = store.cache.Get(key) + if !ok { + value = store.parent.Get(key) + store.cache.SetCached(key, value) + } + return value +} + +func (store *CacheTree) Has(key []byte) bool { + value := store.Get(key) + return value != nil +} + +func (store *CacheTree) Set(key, value []byte) { + storetypes.AssertValidKey(key) + storetypes.AssertValidValue(value) + + store.mtx.Lock() + defer store.mtx.Unlock() + store.cache.Set(key, value) + store.dirty = true +} + +func (store *CacheTree) Delete(key []byte) { + storetypes.AssertValidKey(key) + + store.mtx.Lock() + defer store.mtx.Unlock() + + store.cache.Delete(key) + store.dirty = true +} + +func (store *CacheTree) Iterator(start, end []byte) storetypes.Iterator { + return store.iterator(start, end, true) +} + +func (store *CacheTree) ReverseIterator(start, end []byte) storetypes.Iterator { + return store.iterator(start, end, false) +} + +func (store *CacheTree) Write() { + store.mtx.Lock() + defer store.mtx.Unlock() + + if !store.dirty { + return + } + + // TODO if we are concerned about retaining the whole tree in memory, we could maybe drain the cache using Map.PopMin + store.cache.Scan(func(key, value []byte, dirty bool) bool { + if !dirty { + // TODO we could save these cached reads in the tree but for now we just clear the whole cache + return true + } + + // We use []byte(key) instead of conv.UnsafeStrToBytes because we cannot + // be sure if the underlying store might do a save with the byteslice or + // not. Once we get confirmation that .Delete is guaranteed not to + // save the byteslice, then we can assume only a read-only copy is sufficient. + if value == nil { + store.parent.Delete(key) + } else { + store.parent.Set(key, value) + } + return true + }) + + store.cache.Clear() + store.dirty = false +} + +func (store *CacheTree) iterator(start, end []byte, ascending bool) storetypes.Iterator { + store.mtx.Lock() + defer store.mtx.Unlock() + + isoSortedCache := store.cache.Copy() + + var ( + err error + parent, cache storetypes.Iterator + ) + + if ascending { + parent = store.parent.Iterator(start, end) + cache, err = isoSortedCache.Iterator(start, end) + } else { + parent = store.parent.ReverseIterator(start, end) + cache, err = isoSortedCache.ReverseIterator(start, end) + } + if err != nil { + panic(err) + } + + return internal.NewCacheMergeIterator(parent, cache, ascending) +} + +var _ storetypes.CacheKVStore = (*CacheTree)(nil) diff --git a/iavlx/changeset.go b/iavlx/changeset.go new file mode 100644 index 000000000000..9f872924f102 --- /dev/null +++ b/iavlx/changeset.go @@ -0,0 +1,379 @@ +package iavlx + +import ( + "errors" + "fmt" + "sync/atomic" + "unsafe" +) + +type Changeset struct { + files *ChangesetFiles + info *ChangesetInfo // we copy this so that it can be accessed even in shared changesets + + treeStore *TreeStore + + kvLog *KVLog // TODO make sure we handle compaction here too + branchesData *NodeMmap[BranchLayout] + leavesData *NodeMmap[LeafLayout] + versionsData *StructMmap[VersionInfo] + orphanWriter *OrphanWriter + + refCount atomic.Int32 + evicted atomic.Bool + disposed atomic.Bool + dirtyBranches atomic.Bool + dirtyLeaves atomic.Bool + needsSync atomic.Bool +} + +func NewChangeset(treeStore *TreeStore) *Changeset { + return &Changeset{ + treeStore: treeStore, + } +} + +func OpenChangeset(treeStore *TreeStore, dir string) (*Changeset, error) { + files, err := OpenChangesetFiles(dir) + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + cs := NewChangeset(treeStore) + err = cs.InitOwned(files) + if err != nil { + return nil, fmt.Errorf("failed to initialize changeset: %w", err) + } + return cs, nil +} + +func (cr *Changeset) InitOwned(files *ChangesetFiles) error { + err := cr.InitShared(files) + if err != nil { + return err + } + cr.files = files + return nil +} + +func (cr *Changeset) InitShared(files *ChangesetFiles) error { + var err error + + cr.kvLog, err = NewKVLog(files.kvlogFile) + if err != nil { + return fmt.Errorf("failed to open KV data store: %w", err) + } + + cr.leavesData, err = NewNodeReader[LeafLayout](files.leavesFile) + if err != nil { + return fmt.Errorf("failed to open leaves data file: %w", err) + } + + cr.branchesData, err = NewNodeReader[BranchLayout](files.branchesFile) + if err != nil { + return fmt.Errorf("failed to open branches data file: %w", err) + } + + cr.versionsData, err = NewStructReader[VersionInfo](files.versionsFile) + if err != nil { + return fmt.Errorf("failed to open versions data file: %w", err) + } + + cr.orphanWriter = NewOrphanWriter(files.orphansFile) + + cr.info = files.info + + return nil +} + +func (cr *Changeset) getVersionInfo(version uint32) (*VersionInfo, error) { + info := cr.info + if version < info.StartVersion || version >= info.StartVersion+uint32(cr.versionsData.Count()) { + return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, info.StartVersion, info.StartVersion+uint32(cr.versionsData.Count())-1) + } + return cr.versionsData.UnsafeItem(version - info.StartVersion), nil +} + +func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadK(nodeId) + } + cr.Pin() + defer cr.Unpin() + + k, err := cr.kvLog.UnsafeReadK(offset) + if err != nil { + return nil, err + } + copyKey := make([]byte, len(k)) + copy(copyKey, k) + return copyKey, nil +} + +func (cr *Changeset) ReadKV(nodeId NodeID, offset uint32) (key, value []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadKV(nodeId) + } + cr.Pin() + defer cr.Unpin() + + k, v, err := cr.kvLog.UnsafeReadKV(offset) + if err != nil { + return nil, nil, err + } + copyKey := make([]byte, len(k)) + copy(copyKey, k) + copyValue := make([]byte, len(v)) + copy(copyValue, v) + return copyKey, copyValue, nil +} + +func (cr *Changeset) ReadV(nodeId NodeID, offset uint32) (value []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadV(nodeId) + } + cr.Pin() + defer cr.Unpin() + + _, v, err := cr.kvLog.UnsafeReadKV(offset) + if err != nil { + return nil, err + } + copyValue := make([]byte, len(v)) + copy(copyValue, v) + return copyValue, nil +} + +func (cr *Changeset) ResolveLeaf(nodeId NodeID, fileIdx uint32) (LeafLayout, error) { + if cr.evicted.Load() { + return cr.treeStore.ResolveLeaf(nodeId) + } + cr.Pin() + defer cr.Unpin() + + if fileIdx == 0 { + version := uint32(nodeId.Version()) + vi, err := cr.getVersionInfo(version) + if err != nil { + return LeafLayout{}, err + } + leaf, err := cr.leavesData.FindByID(nodeId, &vi.Leaves) + if err != nil { + return LeafLayout{}, err + } + return *leaf, nil + } else { + fileIdx-- // convert to 0-based index + return *cr.leavesData.UnsafeItem(fileIdx), nil + } +} + +func (cr *Changeset) ResolveBranch(nodeId NodeID, fileIdx uint32) (BranchLayout, error) { + if cr.evicted.Load() { + return cr.treeStore.ResolveBranch(nodeId) + } + + layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) + return layout, err +} + +func (cr *Changeset) resolveBranchWithIdx(nodeId NodeID, fileIdx uint32) (BranchLayout, uint32, error) { + cr.Pin() + defer cr.Unpin() + + if fileIdx == 0 { + version := uint32(nodeId.Version()) + vi, err := cr.getVersionInfo(version) + if err != nil { + return BranchLayout{}, 0, err + } + branch, err := cr.branchesData.FindByID(nodeId, &vi.Branches) + if err != nil { + return BranchLayout{}, 0, err + } + // Compute the actual file index from the pointer + itemIdx := uint32((uintptr(unsafe.Pointer(branch)) - uintptr(unsafe.Pointer(&cr.branchesData.items[0]))) / uintptr(cr.branchesData.size)) + return *branch, itemIdx + 1, nil // +1 to convert back to 1-based + } else { + itemIdx := fileIdx - 1 // convert to 0-based index + return *cr.branchesData.UnsafeItem(itemIdx), fileIdx, nil // return original fileIdx + } +} + +func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { + if cr.evicted.Load() { + return cr.treeStore.Resolve(nodeId, fileIdx) + } + cr.Pin() + defer cr.Unpin() + + // we don't have a fileIdx, so its probably not in this changeset. + if fileIdx == 0 { + // load up the changeset for this node + cs := cr.treeStore.getChangesetForVersion(uint32(nodeId.Version())) + cs.Pin() + defer cs.Unpin() + + // get version data + version := uint32(nodeId.Version()) + vi, err := cs.getVersionInfo(version) + if err != nil { + return nil, err + } + if nodeId.IsLeaf() { + leaf, err := cs.leavesData.FindByID(nodeId, &vi.Leaves) + if err != nil { + return nil, err + } + return &LeafPersisted{ + store: cs, + selfIdx: 0, + layout: *leaf, + }, nil + } else { + branch, err := cs.branchesData.FindByID(nodeId, &vi.Branches) + if err != nil { + return nil, err + } + return &BranchPersisted{ + store: cs, + layout: *branch, + }, nil + } + } else { + // since we have the fileIdx, we know it's in this changeset. + // we can just directly index in this changeset's leaf/branch data. + if nodeId.IsLeaf() { + itemIdx := fileIdx - 1 + leafLayout := *cr.leavesData.UnsafeItem(itemIdx) + return &LeafPersisted{layout: leafLayout, store: cr}, nil + } else { + itemIdx := fileIdx - 1 + branchLayout := *cr.branchesData.UnsafeItem(itemIdx) + return &BranchPersisted{ + layout: branchLayout, + store: cr, + }, nil + } + } +} + +var ErrDisposed = errors.New("changeset disposed") + +func (cr *Changeset) MarkOrphan(version uint32, nodeId NodeID) error { + err := cr.orphanWriter.WriteOrphan(version, nodeId) + if err != nil { + return fmt.Errorf("failed to write orphan node: %w", err) + } + + info := cr.info + if nodeId.IsLeaf() { + info.LeafOrphans++ + info.LeafOrphanVersionTotal += uint64(version) + } else { + info.BranchOrphans++ + info.BranchOrphanVersionTotal += uint64(version) + } + + return nil +} + +func (cr *Changeset) ReadyToCompact(orphanPercentTarget float64, orphanAgeTarget uint32) bool { + info := cr.info + leafOrphanCount := info.LeafOrphans + if leafOrphanCount > 0 { + leafOrphanPercent := float64(leafOrphanCount) / float64(cr.leavesData.Count()) + leafOrphanAge := uint32(info.LeafOrphanVersionTotal / uint64(info.LeafOrphans)) + + if leafOrphanPercent >= orphanPercentTarget && leafOrphanAge <= orphanAgeTarget { + return true + } + } + + branchOrphanCount := info.BranchOrphans + if branchOrphanCount > 0 { + branchOrphanPercent := float64(branchOrphanCount) / float64(cr.branchesData.Count()) + branchOrphanAge := uint32(info.BranchOrphanVersionTotal / uint64(info.BranchOrphans)) + if branchOrphanPercent >= orphanPercentTarget && branchOrphanAge <= orphanAgeTarget { + return true + } + } + + return false +} + +func (cr *Changeset) Close() error { + errs := []error{ + cr.kvLog.Close(), + cr.leavesData.Close(), + cr.branchesData.Close(), + cr.versionsData.Close(), + cr.orphanWriter.Flush(), + } + if cr.files != nil { + errs = append(errs, cr.files.Close()) + } + return errors.Join(errs...) +} + +func (cr *Changeset) Pin() { + cr.refCount.Add(1) +} + +func (cr *Changeset) Unpin() { + cr.refCount.Add(-1) +} + +func (cr *Changeset) Evict() { + cr.evicted.Store(true) +} + +func (cr *Changeset) TryDispose() bool { + if cr.disposed.Load() { + return true + } + if cr.refCount.Load() <= 0 { + if cr.disposed.CompareAndSwap(false, true) { + _ = cr.Close() + cr.versionsData = nil + cr.branchesData = nil + cr.leavesData = nil + cr.kvLog = nil + // DO NOT set treeStore to nil, as deposed changesets should still forward calls to the main tree store + // DO NOT set files to nil, as we might need to delete them later + return true + } + } + return false +} + +func (cr *Changeset) TotalBytes() int { + return cr.leavesData.TotalBytes() + + cr.branchesData.TotalBytes() + + cr.kvLog.TotalBytes() + + cr.versionsData.TotalBytes() +} + +func (cr *Changeset) HasOrphans() bool { + info := cr.info + return info.LeafOrphans > 0 || info.BranchOrphans > 0 +} + +func (cr *Changeset) ResolveRoot(version uint32) (*NodePointer, error) { + startVersion := cr.info.StartVersion + endVersion := startVersion + uint32(cr.versionsData.Count()) - 1 + if version < startVersion || version > endVersion { + return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, startVersion, endVersion) + } + vi, err := cr.getVersionInfo(version) + if err != nil { + return nil, err + } + if vi.RootID == 0 { + // empty tree + return nil, nil + } + return &NodePointer{ + id: vi.RootID, + store: cr, + }, nil +} diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go new file mode 100644 index 000000000000..14262f0a6ac1 --- /dev/null +++ b/iavlx/changeset_files.go @@ -0,0 +1,290 @@ +package iavlx + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +type ChangesetFiles struct { + dir string + treeDir string + startVersion uint32 + compactedAt uint32 + + kvlogFile *os.File + kvlogPath string + branchesFile *os.File + leavesFile *os.File + versionsFile *os.File + orphansFile *os.File + infoFile *os.File + info *ChangesetInfo + + closed bool +} + +func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) { + // ensure absolute path + var err error + treeDir, err = filepath.Abs(treeDir) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path for %s: %w", treeDir, err) + } + + dirName := fmt.Sprintf("%d", startVersion) + if compactedAt > 0 { + dirName = fmt.Sprintf("%d.%d", startVersion, compactedAt) + } + dir := filepath.Join(treeDir, dirName) + + err = os.MkdirAll(dir, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create changeset dir: %w", err) + } + + // create pending marker file for compacted changesets + if compactedAt > 0 { + err := os.WriteFile(filepath.Join(dir, "pending"), []byte{}, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create pending marker file for compacted changeset: %w", err) + } + } + + localKVLogPath := filepath.Join(dir, "kv.log") + if kvlogPath == "" { + // For original (non-compacted) changesets, normalize the path by evaluating + // symlinks in the directory path to ensure consistent comparisons later. + // This handles platform differences like /var vs /private/var on macOS. + normalizedDir, err := filepath.EvalSymlinks(dir) + if err != nil { + return nil, fmt.Errorf("failed to eval directory path: %w", err) + } + kvlogPath = filepath.Join(normalizedDir, "kv.log") + } else { + // create symlink to kvlog so that it can be reopened later + err := os.Symlink(kvlogPath, localKVLogPath) + if err != nil { + return nil, fmt.Errorf("failed to create kvlog symlink: %w", err) + } + kvlogPath, err = filepath.EvalSymlinks(localKVLogPath) + if err != nil { + return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err) + } + } + + cr := &ChangesetFiles{ + dir: dir, + treeDir: treeDir, + startVersion: startVersion, + compactedAt: compactedAt, + kvlogPath: kvlogPath, + } + + err = cr.open(os.O_RDWR | os.O_CREATE | os.O_APPEND) + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + return cr, nil +} + +func OpenChangesetFiles(dirName string) (*ChangesetFiles, error) { + startVersion, compactedAt, valid := ParseChangesetDirName(filepath.Base(dirName)) + if !valid { + return nil, fmt.Errorf("invalid changeset dir name: %s", dirName) + } + + dir, err := filepath.Abs(dirName) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path for %s: %w", dirName, err) + } + + treeDir := filepath.Dir(dir) + + localKVLogPath := filepath.Join(dir, "kv.log") + kvlogPath, err := filepath.EvalSymlinks(localKVLogPath) + if err != nil { + return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err) + } + + cr := &ChangesetFiles{ + dir: dir, + treeDir: treeDir, + startVersion: uint32(startVersion), + compactedAt: uint32(compactedAt), + kvlogPath: kvlogPath, + } + + err = cr.open(os.O_RDWR) + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + + return cr, nil +} + +func (cr *ChangesetFiles) open(mode int) error { + var err error + leavesPath := filepath.Join(cr.dir, "leaves.dat") + + cr.kvlogFile, err = os.OpenFile(cr.kvlogPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create KV log file: %w", err) + } + + cr.leavesFile, err = os.OpenFile(leavesPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create leaves data file: %w", err) + } + + branchesPath := filepath.Join(cr.dir, "branches.dat") + cr.branchesFile, err = os.OpenFile(branchesPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create branches data file: %w", err) + } + + versionsPath := filepath.Join(cr.dir, "versions.dat") + cr.versionsFile, err = os.OpenFile(versionsPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create versions data file: %w", err) + } + + orphansPath := filepath.Join(cr.dir, "orphans.dat") + cr.orphansFile, err = os.OpenFile(orphansPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create orphans data file: %w", err) + } + + infoPath := filepath.Join(cr.dir, "info.dat") + cr.infoFile, err = os.OpenFile(infoPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create changeset info file: %w", err) + } + + cr.info, err = ReadChangesetInfo(cr.infoFile) + if err != nil { + return fmt.Errorf("failed to read changeset info: %w", err) + } + + return nil +} + +func ParseChangesetDirName(dirName string) (startVersion, compactedAt uint64, valid bool) { + var err error + // if no dot, it's an original changeset + if !strings.Contains(dirName, ".") { + startVersion, err = strconv.ParseUint(dirName, 10, 64) + if err != nil { + return 0, 0, false + } + return startVersion, 0, true + } else { + parts := strings.Split(dirName, ".") + if len(parts) != 2 { + return 0, 0, false + } + startVersion, err = strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return 0, 0, false + } + compactedAt, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return 0, 0, false + } + return startVersion, compactedAt, true + } +} + +func (cr *ChangesetFiles) TreeDir() string { + return cr.treeDir +} + +func (cr *ChangesetFiles) KVLogPath() string { + return cr.kvlogFile.Name() +} + +func (cr *ChangesetFiles) StartVersion() uint32 { + return cr.startVersion +} + +func (cr *ChangesetFiles) CompactedAtVersion() uint32 { + return cr.compactedAt +} + +func (cr *ChangesetFiles) Info() *ChangesetInfo { + return cr.info +} + +func (cr *ChangesetFiles) RewriteInfo() error { + return RewriteChangesetInfo(cr.infoFile, cr.info) +} + +func IsChangesetReady(dir string) (bool, error) { + pendingPath := filepath.Join(dir, "pending") + _, err := os.Stat(pendingPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return true, nil + } + return false, fmt.Errorf("failed to stat pending marker file: %w", err) + } + return false, nil +} + +func (cr *ChangesetFiles) MarkReady() error { + pendingPath := filepath.Join(cr.dir, "pending") + err := os.Remove(pendingPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to remove pending marker file: %w", err) + } + return nil +} + +type ChangesetDeleteArgs struct { + SaveKVLogPath string +} + +func (cr *ChangesetFiles) Close() error { + if cr.closed { + return nil + } + + cr.closed = true + err := errors.Join( + cr.RewriteInfo(), + cr.kvlogFile.Close(), + cr.branchesFile.Close(), + cr.leavesFile.Close(), + cr.versionsFile.Close(), + cr.orphansFile.Close(), + cr.infoFile.Close(), + ) + cr.info = nil + return err +} + +func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error { + errs := []error{ + os.Remove(cr.infoFile.Name()), + os.Remove(cr.leavesFile.Name()), + os.Remove(cr.branchesFile.Name()), + os.Remove(cr.versionsFile.Name()), + os.Remove(cr.orphansFile.Name()), + } + + localKVLogPath := filepath.Join(cr.dir, "kv.log") + if cr.kvlogPath != args.SaveKVLogPath { + // delete the local kv.log (which might be a symlink) + errs = append(errs, os.Remove(localKVLogPath)) + } + err := errors.Join(errs...) + if err != nil { + return fmt.Errorf("failed to delete changeset files: %w", err) + } + // delete dir if empty + _ = os.Remove(cr.dir) + return nil +} diff --git a/iavlx/changeset_info.go b/iavlx/changeset_info.go new file mode 100644 index 000000000000..34b21040b68e --- /dev/null +++ b/iavlx/changeset_info.go @@ -0,0 +1,61 @@ +package iavlx + +import ( + "fmt" + "io" + "os" + "unsafe" +) + +type ChangesetInfo struct { + StartVersion uint32 + EndVersion uint32 + LeafOrphans uint32 + BranchOrphans uint32 + LeafOrphanVersionTotal uint64 + BranchOrphanVersionTotal uint64 +} + +// RewriteChangesetInfo truncates and rewrites the info file with the given changeset info. +func RewriteChangesetInfo(file *os.File, info *ChangesetInfo) error { + if err := file.Truncate(0); err != nil { + return fmt.Errorf("failed to truncate info file: %w", err) + } + if _, err := file.Seek(0, 0); err != nil { + return fmt.Errorf("failed to seek info file: %w", err) + } + + size := int(unsafe.Sizeof(*info)) + data := unsafe.Slice((*byte)(unsafe.Pointer(info)), size) + if _, err := file.Write(data); err != nil { + return fmt.Errorf("failed to write changeset info: %w", err) + } + + return nil +} + +// ReadChangesetInfo reads changeset info from a file. Returns an empty default struct if file is zero length. +func ReadChangesetInfo(file *os.File) (*ChangesetInfo, error) { + stat, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat info file: %w", err) + } + + if stat.Size() == 0 { + return &ChangesetInfo{}, nil + } + + var info ChangesetInfo + size := int(unsafe.Sizeof(info)) + + if stat.Size() != int64(size) { + return nil, fmt.Errorf("info file has unexpected size: %d, expected %d", stat.Size(), size) + } + + buf := make([]byte, size) + if _, err := io.ReadFull(file, buf); err != nil { + return nil, fmt.Errorf("failed to read changeset info: %w", err) + } + + return (*ChangesetInfo)(unsafe.Pointer(&buf[0])), nil +} diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go new file mode 100644 index 000000000000..a08f1410e0d1 --- /dev/null +++ b/iavlx/changeset_writer.go @@ -0,0 +1,273 @@ +package iavlx + +import ( + "errors" + "fmt" + "sync/atomic" + "unsafe" +) + +type ChangesetWriter struct { + stagedVersion uint32 + + files *ChangesetFiles + needsSync atomic.Bool + + kvlog *KVLogWriter + branchesData *StructWriter[BranchLayout] + leavesData *StructWriter[LeafLayout] + versionsData *StructWriter[VersionInfo] + + reader *Changeset + + keyCache map[string]uint32 +} + +func NewChangesetWriter(treeDir string, startVersion uint32, treeStore *TreeStore) (*ChangesetWriter, error) { + files, err := CreateChangesetFiles(treeDir, startVersion, 0, "") + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + + cs := &ChangesetWriter{ + stagedVersion: startVersion, + files: files, + kvlog: NewKVDataWriter(files.kvlogFile), + branchesData: NewStructWriter[BranchLayout](files.branchesFile), + leavesData: NewStructWriter[LeafLayout](files.leavesFile), + versionsData: NewStructWriter[VersionInfo](files.versionsFile), + reader: NewChangeset(treeStore), + keyCache: make(map[string]uint32), + } + return cs, nil +} + +func (cs *ChangesetWriter) WriteWALUpdates(updates []KVUpdate) error { + return cs.kvlog.WriteUpdates(updates) +} + +func (cs *ChangesetWriter) WriteWALCommit(version uint32) error { + return cs.kvlog.WriteCommit(version) +} + +func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version, totalLeaves, totalBranches uint32) error { + cs.needsSync.Store(true) + + if version != cs.stagedVersion { + return fmt.Errorf("version mismatch: expected %d, got %d", cs.stagedVersion, version) + } + + var versionInfo VersionInfo + versionInfo.Branches.StartOffset = uint32(cs.branchesData.Count()) + versionInfo.Leaves.StartOffset = uint32(cs.leavesData.Count()) + if totalBranches > 0 { + versionInfo.Branches.StartIndex = 1 + versionInfo.Branches.Count = totalBranches + versionInfo.Branches.EndIndex = totalBranches + } + if totalLeaves > 0 { + versionInfo.Leaves.StartIndex = 1 + versionInfo.Leaves.Count = totalLeaves + versionInfo.Leaves.EndIndex = totalLeaves + } + + if root != nil { + err := cs.writeNode(root) + if err != nil { + return err + } + + versionInfo.RootID = root.id + } + + // commit version info + err := cs.versionsData.Append(&versionInfo) + if err != nil { + return fmt.Errorf("failed to write version info: %w", err) + } + + // Set start version on first successful save + info := cs.files.info + if info.StartVersion == 0 { + info.StartVersion = version + } + + // Always update end version + info.EndVersion = version + + cs.stagedVersion++ + + return nil +} + +func (cs *ChangesetWriter) CreatedSharedReader() (*Changeset, error) { + err := cs.Flush() + if err != nil { + return nil, fmt.Errorf("failed to flush data before creating shared reader: %w", err) + } + + err = cs.reader.InitShared(cs.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize shared changeset reader: %w", err) + } + + reader := cs.reader + cs.reader = NewChangeset(reader.treeStore) + return reader, nil +} + +func (cs *ChangesetWriter) Flush() error { + return errors.Join( + cs.files.RewriteInfo(), + cs.leavesData.Flush(), + cs.branchesData.Flush(), + cs.kvlog.Flush(), + cs.versionsData.Flush(), + ) +} + +func (cs *ChangesetWriter) writeNode(np *NodePointer) error { + memNode := np.mem.Load() + if memNode == nil { + return nil // already persisted + } + if memNode.version != cs.stagedVersion { + return nil // not part of this version + } + if memNode.IsLeaf() { + return cs.writeLeaf(np, memNode) + } else { + return cs.writeBranch(np, memNode) + } +} + +func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error { + // recursively write children in post-order traversal + err := cs.writeNode(node.left) + if err != nil { + return err + } + err = cs.writeNode(node.right) + if err != nil { + return err + } + + // TODO cache key offset in memory to avoid duplicate writes + keyOffset, ok := cs.keyCache[unsafeBytesToString(node.key)] + if !ok { + var err error + keyOffset, err = cs.kvlog.WriteK(node.key) + if err != nil { + return fmt.Errorf("failed to write key data: %w", err) + } + } + + leftVersion := node.left.id.Version() + rightVersion := node.right.id.Version() + + var leftOffset uint32 + var rightOffset uint32 + + // If the child node is in the same changeset, store its 1-based file offset. + // fileIdx is already 1-based (set to Count() after append), and 0 means no offset. + if leftVersion >= uint64(cs.StartVersion()) { + leftOffset = node.left.fileIdx + } + if rightVersion >= uint64(cs.StartVersion()) { + rightOffset = node.right.fileIdx + } + + layout := BranchLayout{ + Id: np.id, + Left: node.left.id, + Right: node.right.id, + LeftOffset: leftOffset, + RightOffset: rightOffset, + KeyOffset: keyOffset, + Height: node.height, + Size: uint32(node.size), // TODO check overflow + } + copy(layout.Hash[:], node.hash) // TODO check length + + err = cs.branchesData.Append(&layout) // TODO check error + if err != nil { + return fmt.Errorf("failed to write branch node: %w", err) + } + + np.fileIdx = uint32(cs.branchesData.Count()) + np.store = cs.reader + + return nil +} + +func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { + keyOffset := node.keyOffset + if keyOffset == 0 { + var err error + keyOffset, err = cs.kvlog.WriteKV(node.key, node.value) + if err != nil { + return fmt.Errorf("failed to write key-value data: %w", err) + } + } + + layout := LeafLayout{ + Id: np.id, + KeyOffset: keyOffset, + } + copy(layout.Hash[:], node.hash) // TODO check length + + err := cs.leavesData.Append(&layout) + if err != nil { + return fmt.Errorf("failed to write leaf node: %w", err) + } + + np.fileIdx = uint32(cs.leavesData.Count()) + np.store = cs.reader + + cs.keyCache[unsafeBytesToString(node.key)] = keyOffset + + return nil +} + +func (cs *ChangesetWriter) TotalBytes() int { + return cs.leavesData.Size() + + cs.branchesData.Size() + + cs.versionsData.Size() + + cs.kvlog.Size() +} + +func (cs *ChangesetWriter) Seal() (*Changeset, error) { + err := cs.Flush() + if err != nil { + return nil, fmt.Errorf("failed to flush changeset data: %w", err) + } + + err = cs.reader.InitOwned(cs.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize owned changeset reader: %w", err) + } + cs.leavesData = nil + cs.branchesData = nil + cs.versionsData = nil + cs.kvlog = nil + cs.keyCache = nil + reader := cs.reader + cs.reader = nil + + return reader, nil +} + +func (cs *ChangesetWriter) StartVersion() uint32 { + return cs.files.StartVersion() +} + +func (cs *ChangesetWriter) SyncWAL() error { + if !cs.needsSync.CompareAndSwap(true, false) { + return nil + } + return cs.files.kvlogFile.Sync() +} + +func unsafeBytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} diff --git a/iavlx/cleanup.go b/iavlx/cleanup.go new file mode 100644 index 000000000000..996e1f35dae1 --- /dev/null +++ b/iavlx/cleanup.go @@ -0,0 +1,436 @@ +package iavlx + +import ( + "context" + "errors" + "fmt" + "log/slog" + "sync" + "time" + + "go.opentelemetry.io/otel" +) + +var ( + tracer = otel.Tracer("iavlx") +) + +type cleanupProc struct { + *TreeStore + closeCleanupProc chan struct{} + cleanupProcDone chan struct{} + + // Split orphan queues based on whether versions are readable + orphanWriteQueue []markOrphansReq // For versions <= savedVersion (can process immediately) + stagedOrphanQueue []markOrphansReq // For versions > savedVersion (need to wait) + orphanQueueLock sync.Mutex + + toDelete map[*Changeset]ChangesetDeleteArgs + activeCompactor *Compactor + beingCompacted []compactionEntry + + // Disposal queue for evicted changesets awaiting refcount=0 + disposalQueue sync.Map // *Changeset -> struct{} +} + +type compactionEntry struct { + entry *changesetEntry + cs *Changeset +} + +func newCleanupProc(treeStore *TreeStore) *cleanupProc { + cp := &cleanupProc{ + TreeStore: treeStore, + closeCleanupProc: make(chan struct{}), + cleanupProcDone: make(chan struct{}), + toDelete: make(map[*Changeset]ChangesetDeleteArgs), + } + go cp.run() + return cp +} + +func (cp *cleanupProc) run() { + ctx, span := tracer.Start(context.Background(), "cleanupProc") + defer span.End() + // before we shutdown save any pending orphans + defer func() { + err := cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans at shutdown", "error", err) + } + }() + defer close(cp.cleanupProcDone) + + minCompactorInterval := time.Second * time.Duration(cp.opts.MinCompactionSeconds) + var lastCompactorStart time.Time + + for { + sleepTime := time.Duration(0) + if time.Since(lastCompactorStart) < minCompactorInterval { + sleepTime = minCompactorInterval - time.Since(lastCompactorStart) + } + select { + case <-cp.closeCleanupProc: + return + case <-time.After(sleepTime): + } + + lastCompactorStart = time.Now() + + // process any pending orphans at the start of each cycle + err := cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans at start of cycle", "error", err) + } + + // collect current entries + cp.changesetsMapLock.RLock() + var entries []*changesetEntry + cp.changesets.Scan(func(version uint32, entry *changesetEntry) bool { + entries = append(entries, entry) + return true + }) + cp.changesetsMapLock.RUnlock() + + for i := 0; i < len(entries); i++ { + entry := entries[i] + var nextEntry *changesetEntry + if i+1 < len(entries) { + nextEntry = entries[i+1] + } + err := cp.processEntry(ctx, entry, nextEntry) + if err != nil { + cp.logger.Error("failed to process changeset entry", "error", err) + // on error, clean up any failed compaction and stop processing further entries this round + cp.cleanupFailedCompaction() + break + } + } + if cp.activeCompactor != nil { + err := cp.sealActiveCompactor() + if err != nil { + cp.logger.Error("failed to seal active compactor", "error", err) + } + } + + cp.processToDelete() + cp.processDisposalQueue() + } +} + +func (cp *cleanupProc) markOrphans(version uint32, nodeIds [][]NodeID) { + req := markOrphansReq{ + version: version, + orphans: nodeIds, + } + + cp.orphanQueueLock.Lock() + defer cp.orphanQueueLock.Unlock() + + cp.orphanWriteQueue = append(cp.orphanWriteQueue, req) +} + +// doMarkOrphans must only be called from the cleanupProc +func (cp *cleanupProc) doMarkOrphans() error { + var orphanQueue []markOrphansReq + cp.orphanQueueLock.Lock() + orphanQueue, cp.orphanWriteQueue = cp.orphanWriteQueue, nil + cp.orphanQueueLock.Unlock() + + orphanQueue = append(orphanQueue, cp.stagedOrphanQueue...) + + savedVersion := cp.savedVersion.Load() + var newStagedOrphans []markOrphansReq + + for _, req := range orphanQueue { + for _, nodeSet := range req.orphans { + var stagedNodes []NodeID + for _, nodeId := range nodeSet { + nodeVersion := uint32(nodeId.Version()) + + // Route to staged queue if version not yet readable + if nodeVersion > savedVersion { + stagedNodes = append(stagedNodes, nodeId) + continue + } + + ce := cp.getChangesetEntryForVersion(nodeVersion) + if ce == nil { + return fmt.Errorf("no changeset found for version %d", nodeVersion) + } + // this somewhat awkward retry loop is needed to handle a race condition where + // we have disposed of a changeset between getting the entry and marking the orphan + retries := 0 + for { + err := ce.changeset.Load().MarkOrphan(req.version, nodeId) + if errors.Is(err, ErrDisposed) { + if retries > 3 { + return fmt.Errorf("changeset for version %d disposed while marking orphan %s", nodeVersion, nodeId.String()) + } + retries++ + continue + } else if err != nil { + return err + } + break + } + } + // Add any staged nodes back to the staged queue + if len(stagedNodes) > 0 { + newStagedOrphans = append(newStagedOrphans, markOrphansReq{ + version: req.version, + orphans: [][]NodeID{stagedNodes}, + }) + } + } + } + + cp.stagedOrphanQueue = newStagedOrphans + + return nil +} + +func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *changesetEntry) error { + ctx, span := tracer.Start(ctx, "cleanupProc.processEntry") + defer span.End() + + cs := entry.changeset.Load() + + if cs.files == nil { + // skipping incomplete changeset which is still open for writing + return nil + } + + // safety check - skip if evicted or disposed + if cs.evicted.Load() || cs.disposed.Load() { + return fmt.Errorf("evicted/disposed changeset: %s found in queue", cs.files.dir) + } + + if cp.opts.DisableCompaction { + return nil + } + + // skip if still pending sync + if cs.needsSync.Load() { + return nil + } + + if cp.activeCompactor != nil { + if cp.opts.CompactWAL && + cs.TotalBytes()+cp.activeCompactor.TotalBytes() <= int(cp.opts.GetCompactionMaxTarget()) { + // add to active compactor + slog.DebugContext(ctx, "joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir, + "newDir", cp.activeCompactor.files.dir) + err := cp.activeCompactor.AddChangeset(cs) + if err != nil { + return fmt.Errorf("failed to add changeset to active compactor: %w", err) + } + cp.beingCompacted = append(cp.beingCompacted, compactionEntry{entry: entry, cs: cs}) + return nil + } else { + err := cp.sealActiveCompactor() + if err != nil { + cp.cleanupFailedCompaction() + return fmt.Errorf("failed to seal active compactor: %w", err) + } + } + } + + // mark any pending orphans here when we don't have an active compactor + err := cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans", "error", err) + } + + // check if other triggers apply for a new compaction + savedVersion := cp.savedVersion.Load() + retainVersions := cp.opts.RetainVersions + retentionWindowBottom := savedVersion - retainVersions + if retainVersions == 0 { + // retain everything + retentionWindowBottom = 0 + } + + compactOrphanAge := cp.opts.GetCompactionOrphanAge() + compactOrphanThreshold := cp.opts.GetCompactionOrphanRatio() + + // Age target relative to bottom of retention window + ageTarget := retentionWindowBottom - compactOrphanAge + + // Check orphan-based trigger + shouldCompact := cs.ReadyToCompact(compactOrphanThreshold, ageTarget) + if !shouldCompact { + lastCompactedAt := cs.files.CompactedAtVersion() + if savedVersion-lastCompactedAt >= cp.opts.GetCompactAfterVersions() { + shouldCompact = cs.HasOrphans() + } + } + + // Check size-based joining trigger + maxSize := cp.opts.GetCompactionMaxTarget() + + canJoin := false + if !shouldCompact && cp.opts.CompactWAL && nextEntry != nil { + nextCs := nextEntry.changeset.Load() + if nextCs.files != nil && // we can't compact a changeset that's still being written + nextCs.info.StartVersion == cs.info.EndVersion+1 { + if uint64(cs.TotalBytes())+uint64(nextCs.TotalBytes()) <= maxSize { + canJoin = true + } + } + } + + if !shouldCompact && !canJoin { + return nil + } + + retainVersion := retentionWindowBottom + retainCriteria := func(createVersion, orphanVersion uint32) bool { + // orphanVersion should be non-zero + if orphanVersion >= retainVersion { + // keep the orphan if it's in the retain window + return true + } else { + // otherwise, we can remove it + return false + } + } + + slog.Info("compacting changeset", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir) + + cp.activeCompactor, err = NewCompacter(ctx, cs, CompactOptions{ + RetainCriteria: retainCriteria, + CompactWAL: cp.opts.CompactWAL, + CompactedAt: savedVersion, + }, cp.TreeStore) + if err != nil { + return fmt.Errorf("failed to create compactor: %w", err) + } + cp.beingCompacted = []compactionEntry{{entry: entry, cs: cs}} + return nil +} + +func (cp *cleanupProc) sealActiveCompactor() error { + // seal compactor and finish + newCs, err := cp.activeCompactor.Seal() + if err != nil { + return fmt.Errorf("failed to seal active compactor: %w", err) + } + + // update all processed entries to point to new changeset + oldSize := uint64(0) + for i, procEntry := range cp.beingCompacted { + cp.logger.Debug("updating changeset entry to compacted changeset and trying to delete", + "old_dir", procEntry.cs.files.dir, "new_dir", newCs.files.dir) + + oldCs := procEntry.cs + oldDir := oldCs.files.dir + oldSize += uint64(oldCs.TotalBytes()) + + if i == 0 { + procEntry.entry.changeset.Store(newCs) + } else { + cp.changesetsMapLock.Lock() + cp.changesets.Delete(oldCs.files.StartVersion()) + cp.changesetsMapLock.Unlock() + } + oldCs.Evict() + + // try to delete now or schedule for later + if !oldCs.TryDispose() { + cp.logger.Debug("changeset has active references, scheduling for deletion", "path", oldDir, "refcount", oldCs.refCount.Load()) + cp.toDelete[oldCs] = ChangesetDeleteArgs{newCs.files.KVLogPath()} + } else { + cp.logger.Info("changeset disposed, deleting files", "path", oldDir) + err = oldCs.files.DeleteFiles(ChangesetDeleteArgs{SaveKVLogPath: newCs.files.KVLogPath()}) + if err != nil { + cp.logger.Error("failed to delete old changeset files", "error", err, "path", oldDir) + } + } + } + + cp.logger.Info("compacted changeset", "dir", newCs.files.dir, "new_size", newCs.TotalBytes(), "old_size", oldSize, "joined", len(cp.beingCompacted)) + + // Clear compactor state after successful seal + cp.activeCompactor = nil + cp.beingCompacted = nil + return nil +} + +func (cp *cleanupProc) cleanupFailedCompaction() { + // clean up any partial compactor state and remove temporary files + if cp.activeCompactor != nil && cp.activeCompactor.files != nil { + cp.logger.Warn("cleaning up failed compaction", "dir", cp.activeCompactor.files.dir, "changesets_attempted", len(cp.beingCompacted)) + err := cp.activeCompactor.Abort() + if err != nil { + cp.logger.Error("failed to abort active compactor", "error", err) + } + } + cp.activeCompactor = nil + cp.beingCompacted = nil +} + +func (cp *cleanupProc) processToDelete() { + if len(cp.toDelete) > 0 { + cp.logger.Debug("processing delete queue", "size", len(cp.toDelete)) + } + + for oldCs, args := range cp.toDelete { + select { + case <-cp.closeCleanupProc: + return + default: + } + + if !oldCs.TryDispose() { + cp.logger.Warn("old changeset not disposed, skipping delete", "path", oldCs.files.dir, "refcount", oldCs.refCount.Load()) + continue + } + + cp.logger.Info("deleting old changeset files", "path", oldCs.files.dir) + err := oldCs.files.DeleteFiles(args) + if err != nil { + cp.logger.Error("failed to delete old changeset files", "error", err) + } + delete(cp.toDelete, oldCs) + } +} + +func (cp *cleanupProc) shutdown() { + close(cp.closeCleanupProc) + <-cp.cleanupProcDone +} + +// addPendingDisposal adds an evicted changeset to the disposal queue +func (cp *cleanupProc) addPendingDisposal(cs *Changeset) { + cp.disposalQueue.Store(cs, struct{}{}) +} + +// processDisposalQueue tries to dispose changesets waiting for refcount=0 +func (cp *cleanupProc) processDisposalQueue() { + disposalCount := 0 + cp.disposalQueue.Range(func(key, value interface{}) bool { + disposalCount++ + return true + }) + + if disposalCount > 0 { + cp.logger.Debug("processing disposal queue", "size", disposalCount) + } + + cp.disposalQueue.Range(func(key, value interface{}) bool { + cs := key.(*Changeset) + if cs.TryDispose() { + cp.disposalQueue.Delete(cs) + cp.logger.Debug("disposed shared changeset from queue") + } else { + cp.logger.Debug("shared changeset still has references", "refcount", cs.refCount.Load()) + } + return true + }) + + // Warn if the disposal queue is getting large + if disposalCount > 100 { + cp.logger.Warn("disposal queue is large", "size", disposalCount) + } +} diff --git a/iavlx/commit_multi_tree.go b/iavlx/commit_multi_tree.go new file mode 100644 index 000000000000..a41fbc259de0 --- /dev/null +++ b/iavlx/commit_multi_tree.go @@ -0,0 +1,433 @@ +package iavlx + +import ( + "bytes" + "fmt" + io "io" + "os" + "path/filepath" + "sync" + + dbm "github.com/cosmos/cosmos-db" + protoio "github.com/cosmos/gogoproto/io" + + "cosmossdk.io/log" + + "cosmossdk.io/store/mem" + "cosmossdk.io/store/metrics" + pruningtypes "cosmossdk.io/store/pruning/types" + snapshottypes "cosmossdk.io/store/snapshots/types" + "cosmossdk.io/store/transient" + storetypes "cosmossdk.io/store/types" +) + +type CommitMultiTree struct { + dir string + opts Options + logger log.Logger + trees []storetypes.CommitStore // always ordered by tree name + treeKeys []storetypes.StoreKey // always ordered by tree name + storeTypes []storetypes.StoreType // store types by tree index + treesByKey map[storetypes.StoreKey]int // index of the trees by name + + version uint64 + lastCommitId storetypes.CommitID + workingCommitInfo *storetypes.CommitInfo + workingHash []byte +} + +// GetObjKVStore returns a mounted ObjKVStore for a given StoreKey. +func (db *CommitMultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore { + treeIdx, ok := db.treesByKey[key] + if !ok { + panic(fmt.Sprintf("tree key not found in treesByKey: %v", key)) + } + s := db.trees[treeIdx] + if s == nil { + panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) + } + store, ok := s.(storetypes.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + + return store +} + +func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { + return db.lastCommitId +} + +func (db *CommitMultiTree) WorkingHash() []byte { + // NOTE: this may invoke some hash recomputation each time even if there is no change + stagedVersion := db.version + 1 + hashes := make([][]byte, len(db.trees)) + var wg sync.WaitGroup + for i, tree := range db.trees { + wg.Add(1) + go func(i int, t storetypes.CommitStore) { + defer wg.Done() + hashes[i] = t.WorkingHash() + }(i, tree) + } + wg.Wait() + + commitInfo := &storetypes.CommitInfo{} + commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys)) + for i, treeKey := range db.treeKeys { + commitInfo.StoreInfos[i] = storetypes.StoreInfo{ + Name: treeKey.Name(), + CommitId: storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hashes[i], + }, + } + } + db.workingCommitInfo = commitInfo + hash := commitInfo.Hash() + db.workingHash = hash + return hash +} + +func (db *CommitMultiTree) Commit() storetypes.CommitID { + // NOTE: this function is maybe unnecessarily complex because the SDK has both WorkingHash and Commit methods + // and we're trying to avoid recomputing the hash + // so we check if we already have a hash that was computed in WorkingHash that hasn't changed to avoid recomputation + // in the future we should evaluate if there is any need to retain both WorkingHash and Commit methods separately + hashes := make([][]byte, len(db.trees)) + var wg sync.WaitGroup + for i, tree := range db.trees { + wg.Add(1) + go func(i int, t storetypes.CommitStore) { + defer wg.Done() + hashes[i] = t.Commit().Hash + }(i, tree) + } + wg.Wait() + + stagedVersion := db.version + 1 + commitInfo := db.workingCommitInfo + var hash []byte + if commitInfo == nil { + commitInfo = &storetypes.CommitInfo{} + commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys)) + for i, treeKey := range db.treeKeys { + commitInfo.StoreInfos[i] = storetypes.StoreInfo{ + Name: treeKey.Name(), + CommitId: storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hashes[i], + }, + } + } + hash = commitInfo.Hash() + } else { + hashChanged := false + for i, storeInfo := range commitInfo.StoreInfos { + if !bytes.Equal(storeInfo.CommitId.Hash, hashes[i]) { + hashChanged = true + commitInfo.StoreInfos[i].CommitId.Hash = hashes[i] + } + } + if !hashChanged { + hash = db.workingHash + } else { + hash = commitInfo.Hash() + } + db.workingCommitInfo = nil + db.workingHash = nil + } + + db.version++ + commitId := storetypes.CommitID{ + Version: int64(db.version), + Hash: hash, + } + db.lastCommitId = commitId + return commitId +} + +func (db *CommitMultiTree) SetPruning(options pruningtypes.PruningOptions) { + db.logger.Warn("SetPruning is not implemented for CommitMultiTree") +} + +func (db *CommitMultiTree) GetPruning() pruningtypes.PruningOptions { + return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault) +} + +func (db *CommitMultiTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti +} + +func (db *CommitMultiTree) CacheWrap() storetypes.CacheWrap { + return db.CacheMultiStore() +} + +func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO implement tracking + return db.CacheMultiStore() +} + +func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore { + mt := &MultiTree{ + trees: make([]storetypes.CacheWrap, len(db.trees)), + treesByKey: db.treesByKey, // share the map + } + for i, tree := range db.trees { + mt.trees[i] = tree.CacheWrap() + } + return mt +} + +func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + if version == 0 { + version = int64(db.version) + } + + mt := &MultiTree{ + latestVersion: version, + treesByKey: db.treesByKey, // share the map + trees: make([]storetypes.CacheWrap, len(db.trees)), + } + + for i, tree := range db.trees { + typ := db.storeTypes[i] + switch typ { + case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: + t, err := tree.(*CommitTree).GetImmutable(version) + if err != nil { + return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err) + } + mt.trees[i] = t.CacheWrap() + default: + mt.trees[i] = tree.CacheWrap() + } + } + + return mt, nil +} + +func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + index, ok := db.treesByKey[key] + if !ok { + panic(fmt.Sprintf("store not found for key: %s (key type: %T)", key.Name(), key)) + } + if index >= len(db.trees) { + panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(db.trees))) + } + s := db.trees[index] + store, ok := s.(storetypes.KVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not KVStore", key)) + } + return store +} + +func (db *CommitMultiTree) TracingEnabled() bool { + return false +} + +func (db *CommitMultiTree) SetTracer(w io.Writer) storetypes.MultiStore { + db.logger.Warn("SetTracer is not implemented for CommitMultiTree") + return db +} + +func (db *CommitMultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + db.logger.Warn("SetTracingContext is not implemented for CommitMultiTree") + return db +} + +func (db *CommitMultiTree) Snapshot(height uint64, protoWriter protoio.Writer) error { + return fmt.Errorf("snapshotting has not been implemented yet") +} + +func (db *CommitMultiTree) PruneSnapshotHeight(height int64) { + db.logger.Warn("PruneSnapshotHeight is not implemented for CommitMultiTree") +} + +func (db *CommitMultiTree) SetSnapshotInterval(snapshotInterval uint64) { + db.logger.Warn("SetSnapshotInterval is not implemented for CommitMultiTree") +} + +func (db *CommitMultiTree) Restore(height uint64, format uint32, protoReader protoio.Reader) (snapshottypes.SnapshotItem, error) { + return snapshottypes.SnapshotItem{}, fmt.Errorf("restoring from snapshot has not been implemented yet") +} + +func (db *CommitMultiTree) MountStoreWithDB(key storetypes.StoreKey, typ storetypes.StoreType, _ dbm.DB) { + if _, exists := db.treesByKey[key]; exists { + panic(fmt.Sprintf("store with key %s already mounted", key.Name())) + } + index := len(db.treeKeys) + db.treesByKey[key] = index + db.treeKeys = append(db.treeKeys, key) + db.storeTypes = append(db.storeTypes, typ) +} + +func (db *CommitMultiTree) GetCommitStore(key storetypes.StoreKey) storetypes.CommitStore { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) GetCommitKVStore(key storetypes.StoreKey) storetypes.CommitKVStore { + s := db.trees[db.treesByKey[key]] + store, ok := s.(storetypes.CommitKVStore) + if !ok { + panic(fmt.Sprintf("store with key %s is not CommitKVStore", key.Name())) + } + return store +} + +func (db *CommitMultiTree) LoadLatestVersion() error { + for i, key := range db.treeKeys { + storeType := db.storeTypes[i] + tree, err := db.loadStore(key, storeType) + if err != nil { + return fmt.Errorf("failed to load store %s: %w", key.Name(), err) + } + db.trees = append(db.trees, tree) + } + return nil +} + +func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitStore, error) { + switch typ { + case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: + dir := filepath.Join(db.dir, key.Name()) + if _, err := os.Stat(dir); !os.IsNotExist(err) { + return nil, fmt.Errorf("store directory %s already exists, reloading isn't supported yet", dir) + } + err := os.MkdirAll(dir, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create store dir %s: %w", dir, err) + } + return NewCommitTree(dir, db.opts, db.logger.With("store", key.Name())) + case storetypes.StoreTypeTransient: + _, ok := key.(*storetypes.TransientStoreKey) + if !ok { + return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) + } + + return transient.NewStore(), nil + case storetypes.StoreTypeMemory: + if _, ok := key.(*storetypes.MemoryStoreKey); !ok { + return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String()) + } + + return mem.NewStore(), nil + case storetypes.StoreTypeObject: + if _, ok := key.(*storetypes.ObjectStoreKey); !ok { + return nil, fmt.Errorf("unexpected key type for a ObjectStoreKey: %s", key.String()) + } + return transient.NewObjStore(), nil + default: + return nil, fmt.Errorf("unsupported store type: %s", typ.String()) + } +} + +func (db *CommitMultiTree) LoadLatestVersionAndUpgrade(upgrades *storetypes.StoreUpgrades) error { + return fmt.Errorf("LoadLatestVersionAndUpgrade has not been implemented yet") +} + +func (db *CommitMultiTree) LoadVersionAndUpgrade(ver int64, upgrades *storetypes.StoreUpgrades) error { + return fmt.Errorf("LoadVersionAndUpgrade has not been implemented yet") +} + +func (db *CommitMultiTree) LoadVersion(ver int64) error { + return fmt.Errorf("LoadVersion has not been implemented yet") +} + +func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersistentCache) { + db.logger.Warn("SetInterBlockCache is not implemented for CommitMultiTree") +} + +func (db *CommitMultiTree) SetInitialVersion(version int64) error { + return fmt.Errorf("SetInitialVersion has not been implemented yet") +} + +func (db *CommitMultiTree) SetIAVLCacheSize(size int) { +} + +func (db *CommitMultiTree) SetIAVLDisableFastNode(disable bool) { +} + +func (db *CommitMultiTree) SetIAVLSyncPruning(sync bool) { +} + +func (db *CommitMultiTree) RollbackToVersion(version int64) error { + return fmt.Errorf("RollbackToVersion has not been implemented yet") +} + +func (db *CommitMultiTree) ListeningEnabled(key storetypes.StoreKey) bool { + db.logger.Warn("ListeningEnabled is not implemented for CommitMultiTree") + return false +} + +func (db *CommitMultiTree) AddListeners(keys []storetypes.StoreKey) { + db.logger.Warn("AddListeners is not implemented for CommitMultiTree") +} + +func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair { + // TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) { + db.logger.Warn("SetMetrics is not implemented for CommitMultiTree") +} + +func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) { + // n := len(treeNames) + //trees := make([]*CommitTree, n) + //treesByName := make(map[string]int, n) + //for i, name := range treeNames { + // if _, exists := treesByName[name]; exists { + // return nil, fmt.Errorf("duplicate tree name: %s", name) + // } + // treesByName[name] = i + // dir := filepath.Join(path, name) + // err := os.MkdirAll(dir, 0o755) + // if err != nil { + // return nil, fmt.Errorf("failed to create tree dir %s: %w", dir, err) + // } + // // Create a logger with tree name context + // treeLogger := logger.With("tree", name) + // trees[i], err = NewCommitTree(dir, *opts, treeLogger) + // if err != nil { + // return nil, fmt.Errorf("failed to load tree %s: %w", name, err) + // } + //} + // + db := &CommitMultiTree{ + dir: path, + opts: *opts, + logger: logger, + treesByKey: make(map[storetypes.StoreKey]int), + } + return db, nil +} + +func (db *CommitMultiTree) stagedVersion() uint64 { + return db.version + 1 +} + +func (db *CommitMultiTree) LatestVersion() int64 { + return int64(db.version) +} + +func (db *CommitMultiTree) Close() error { + for _, tree := range db.trees { + if closer, ok := tree.(io.Closer); ok { + err := closer.Close() + if err != nil { + return err + } + } + return nil + } + return nil +} + +var _ storetypes.CommitMultiStore = &CommitMultiTree{} diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go new file mode 100644 index 000000000000..9c20c5c88b00 --- /dev/null +++ b/iavlx/commit_tree.go @@ -0,0 +1,451 @@ +package iavlx + +import ( + "fmt" + "io" + "sync" + "sync/atomic" + + "cosmossdk.io/log" + + pruningtypes "cosmossdk.io/store/pruning/types" + storetypes "cosmossdk.io/store/types" +) + +type CommitTree struct { + latest atomic.Pointer[NodePointer] + root *NodePointer + writeMutex sync.Mutex + store *TreeStore + zeroCopy bool + + evictionDepth uint8 + evictorRunning atomic.Bool + lastEvictVersion uint32 + + writeWal bool + walQueue *NonBlockingQueue[[]KVUpdate] + walDone <-chan error + + pendingOrphans [][]NodeID + + logger log.Logger + + lastCommitId storetypes.CommitID + commitCtx *commitContext +} + +func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { + ts, err := NewTreeStore(dir, opts, logger) + if err != nil { + return nil, fmt.Errorf("failed to create tree store: %w", err) + } + + var root *NodePointer + var lastCommitId storetypes.CommitID + savedVersion := ts.SavedVersion() + if savedVersion > 0 { + root, err = ts.ResolveRoot(savedVersion) + if err != nil { + return nil, fmt.Errorf("failed to resolve root for saved version %d: %w", savedVersion, err) + } + if root != nil { + rootNode, err := root.Resolve() + if err != nil { + return nil, fmt.Errorf("failed to resolve root node for saved version %d: %w", savedVersion, err) + } + hash := rootNode.Hash() + lastCommitId = storetypes.CommitID{ + Version: int64(savedVersion), + Hash: hash, + } + } + } + + tree := &CommitTree{ + store: ts, + root: root, + lastCommitId: lastCommitId, + zeroCopy: opts.ZeroCopy, + logger: logger, + evictionDepth: opts.EvictDepth, + writeWal: opts.WriteWAL, + } + tree.latest.Store(root) + tree.reinitWalProc() + + return tree, nil +} + +func (c *CommitTree) WorkingHash() []byte { + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + return c.workingHash() +} + +func (c *CommitTree) workingHash() []byte { + // IMPORTANT: this function assumes the write lock is held + + // if we have no root, return empty hash + if c.root == nil { + c.commitCtx = nil + return emptyHash + } + + root := c.root.mem.Load() + if root != nil && root.hash != nil { + // already computed working hash + return root.hash + } + + savedVersion := c.store.SavedVersion() + stagedVersion := c.store.stagedVersion + c.commitCtx = &commitContext{ + version: stagedVersion, + savedVersion: savedVersion, + } + + // compute hash and assign node IDs + hash, err := commitTraverse(c.commitCtx, c.root, 0) + if err != nil { + panic(fmt.Sprintf("failed to compute working hash: %v", err)) + } + return hash +} + +func (c *CommitTree) Commit() storetypes.CommitID { + commitId, err := c.commit() + if err != nil { + panic(fmt.Sprintf("failed to commit: %v", err)) + } + return commitId +} + +func (c *CommitTree) commit() (storetypes.CommitID, error) { + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.writeWal { + c.walQueue.Close() + } + + // compute hash and assign node IDs + hash := c.workingHash() + + stagedVersion := c.store.stagedVersion + if c.writeWal { + // wait for WAL write to complete + err := <-c.walDone + if err != nil { + return storetypes.CommitID{}, err + } + + err = c.store.WriteWALCommit(stagedVersion) + if err != nil { + return storetypes.CommitID{}, err + } + + c.reinitWalProc() + } + + commitCtx := c.commitCtx + if commitCtx == nil { + // make sure we have a non-nil commit context + commitCtx = &commitContext{} + } + err := c.store.SaveRoot(c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) + if err != nil { + return storetypes.CommitID{}, err + } + + c.store.MarkOrphans(stagedVersion, c.pendingOrphans) + c.pendingOrphans = nil + + // start eviction if needed + c.startEvict(c.store.SavedVersion()) + + // cache the committed tree as the latest version + c.latest.Store(c.root) + commitId := storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hash, + } + c.lastCommitId = commitId + c.commitCtx = nil + + return commitId, nil +} + +func (c *CommitTree) LastCommitID() storetypes.CommitID { + return c.lastCommitId +} + +func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) {} + +func (c *CommitTree) GetPruning() pruningtypes.PruningOptions { + return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault) +} + +func (c *CommitTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +func (c *CommitTree) CacheWrap() storetypes.CacheWrap { + return NewCacheTree(c) +} + +func (c *CommitTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO support tracing + return c.CacheWrap() +} + +func (c *CommitTree) Get(key []byte) []byte { + if c.root == nil { + return nil + } + + root, err := c.root.Resolve() + if err != nil { + panic(err) + } + + value, _, err := root.Get(key) + if err != nil { + panic(err) + } + + return value +} + +func (c *CommitTree) Has(key []byte) bool { + return c.Get(key) != nil +} + +func (c *CommitTree) Set(key, value []byte) { + storetypes.AssertValidKey(key) + storetypes.AssertValidValue(value) + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + stagedVersion := c.store.stagedVersion + leafNode := &MemNode{ + height: 0, + size: 1, + version: stagedVersion, + key: key, + value: value, + } + + if c.writeWal { + // start writing this to the WAL asynchronously before we even mutate the tree + c.walQueue.Send([]KVUpdate{{SetNode: leafNode}}) + } + + ctx := &MutationContext{Version: stagedVersion} + newRoot, _, err := setRecursive(c.root, leafNode, ctx) + if err != nil { + panic(err) + } + + c.root = newRoot + c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans) +} + +func (c *CommitTree) Delete(key []byte) { + storetypes.AssertValidKey(key) + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.writeWal { + // start writing this to the WAL asynchronously before we even mutate the tree + c.walQueue.Send([]KVUpdate{{DeleteKey: key}}) + } + + ctx := &MutationContext{Version: c.store.stagedVersion} + _, newRoot, _, err := removeRecursive(c.root, key, ctx) + if err != nil { + panic(err) + } + c.root = newRoot + c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans) +} + +func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator { + return NewIterator(start, end, true, c.root, c.zeroCopy) +} + +func (c *CommitTree) ReverseIterator(start, end []byte) storetypes.Iterator { + return NewIterator(start, end, false, c.root, c.zeroCopy) +} + +func (c *CommitTree) reinitWalProc() { + if !c.writeWal { + return + } + + walQueue := NewNonBlockingQueue[[]KVUpdate]() + walDone := make(chan error, 1) + c.walQueue = walQueue + c.walDone = walDone + + go func() { + for { + batch := walQueue.Receive() + if batch == nil { + close(walDone) + return + } + for _, updates := range batch { + err := c.store.WriteWALUpdates(updates) + if err != nil { + walDone <- err + return + } + } + } + }() +} + +func (c *CommitTree) startEvict(evictVersion uint32) { + if c.evictorRunning.Load() { + // eviction in progress + return + } + + if evictVersion <= c.lastEvictVersion { + // no new version to evict + return + } + + latest := c.latest.Load() + if latest == nil { + // nothing to evict + return + } + + c.logger.Debug("start eviction", "version", evictVersion, "depth", c.evictionDepth) + c.evictorRunning.Store(true) + go func() { + evictedCount := evictTraverse(latest, 0, c.evictionDepth, evictVersion) + c.logger.Debug("eviction completed", "version", evictVersion, "lastEvict", c.lastEvictVersion, "evictedNodes", evictedCount) + c.lastEvictVersion = evictVersion + c.evictorRunning.Store(false) + }() +} + +func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) { + var rootPtr *NodePointer + if version == c.lastCommitId.Version { + rootPtr = c.root + } else { + var err error + rootPtr, err = c.store.ResolveRoot(uint32(version)) + if err != nil { + return nil, err + } + } + return NewImmutableTree(rootPtr), nil +} + +func (c *CommitTree) ResolveRoot(version uint32) (*NodePointer, error) { + if version == 0 { + version = c.store.stagedVersion - 1 + } + return c.store.ResolveRoot(version) +} + +func (c *CommitTree) Version() uint32 { + return c.store.stagedVersion - 1 +} + +func (c *CommitTree) Close() error { + if c.walQueue != nil { + c.walQueue.Close() + // TODO do we need to wait for WAL done?? + } + return c.store.Close() +} + +type commitContext struct { + version uint32 + savedVersion uint32 + branchNodeIdx uint32 + leafNodeIdx uint32 +} + +// commitTraverse performs a post-order traversal of the tree to compute hashes and assign node IDs. +// if it is run multiple times and the tree has been mutated before being committed, node IDs will be reassigned. +func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []byte, err error) { + memNode := np.mem.Load() + if memNode == nil { + node, err := np.Resolve() + if err != nil { + return nil, err + } + return node.Hash(), nil + } + + if memNode.version != ctx.version { + return memNode.hash, nil + } + + var leftHash, rightHash []byte + var id NodeID + if memNode.IsLeaf() { + ctx.leafNodeIdx++ + id = NewNodeID(true, uint64(ctx.version), ctx.leafNodeIdx) + } else { + // post-order traversal + leftHash, err = commitTraverse(ctx, memNode.left, depth+1) + if err != nil { + return nil, err + } + rightHash, err = commitTraverse(ctx, memNode.right, depth+1) + if err != nil { + return nil, err + } + + ctx.branchNodeIdx++ + id = NewNodeID(false, uint64(ctx.version), ctx.branchNodeIdx) + } + np.id = id + memNode.nodeId = id + + if memNode.hash != nil { + // hash previously computed node + return memNode.hash, nil + } + + return computeAndSetHash(memNode, leftHash, rightHash) +} + +func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uint32) (count int) { + // TODO check height, and don't traverse if tree is too short + + memNode := np.mem.Load() + if memNode == nil { + return 0 + } + + // Evict nodes at or below the eviction depth + if memNode.version <= evictVersion && depth >= evictionDepth { + np.mem.Store(nil) + count = 1 + } + + if memNode.IsLeaf() { + return count + } + + // Continue traversing to find nodes to evict + count += evictTraverse(memNode.left, depth+1, evictionDepth, evictVersion) + count += evictTraverse(memNode.right, depth+1, evictionDepth, evictVersion) + return count +} + +var ( + _ storetypes.CommitStore = &CommitTree{} +) diff --git a/iavlx/compactor.go b/iavlx/compactor.go new file mode 100644 index 000000000000..da90a3e09e34 --- /dev/null +++ b/iavlx/compactor.go @@ -0,0 +1,347 @@ +package iavlx + +import ( + "context" + "errors" + "fmt" + "log/slog" +) + +type CompactOptions struct { + RetainCriteria RetainCriteria + CompactWAL bool + CompactedAt uint32 // version at which compaction is done +} + +type RetainCriteria func(createVersion, orphanVersion uint32) bool + +type Compactor struct { + criteria RetainCriteria + compactWAL bool + + processedChangesets []*Changeset + treeStore *TreeStore + + originalKvLogPath string + files *ChangesetFiles + leavesWriter *StructWriter[LeafLayout] + branchesWriter *StructWriter[BranchLayout] + versionsWriter *StructWriter[VersionInfo] + kvlogWriter *KVLogWriter + orphanWriter *OrphanWriter + + keyCache map[string]uint32 + // offsetCache holds the updated 1-based offsets of nodes affected by compacting. + // these are then used to update BranchLayout's left and right offsets. + offsetCache map[NodeID]uint32 + + // Running totals across all processed changesets + leafOrphanCount uint32 + branchOrphanCount uint32 + leafOrphanVersionTotal uint64 + branchOrphanVersionTotal uint64 + ctx context.Context +} + +func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { + if reader.files == nil { + return nil, fmt.Errorf("changeset has no associated files, cannot compact a shared changeset reader which files set to nil") + } + files := reader.files + startingVersion := files.StartVersion() + lastCompactedAt := files.CompactedAtVersion() + if lastCompactedAt >= opts.CompactedAt { + return nil, fmt.Errorf("cannot compact changeset starting at version %d which was last compacted at %d to an earlier or same version %d", + startingVersion, lastCompactedAt, opts.CompactedAt) + } + + // if we're not compacting the WAL, we can reuse the existing KV log path + kvlogPath := reader.files.KVLogPath() + // if we're compacting the WAL, create a new KV log path + if opts.CompactWAL { + kvlogPath = "" + } + + newFiles, err := CreateChangesetFiles(files.TreeDir(), files.StartVersion(), opts.CompactedAt, kvlogPath) + if err != nil { + return nil, fmt.Errorf("failed to open new changeset files: %w", err) + } + + var kvlogWriter *KVLogWriter + // we only need a new KV log writer if we're compacting the WAL, otherwise it should be nil + if opts.CompactWAL { + kvlogWriter = NewKVDataWriter(newFiles.kvlogFile) + } + + c := &Compactor{ + ctx: ctx, + criteria: opts.RetainCriteria, + compactWAL: opts.CompactWAL, + treeStore: store, + files: newFiles, + originalKvLogPath: reader.files.KVLogPath(), + kvlogWriter: kvlogWriter, + leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile), + branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile), + versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), + orphanWriter: NewOrphanWriter(newFiles.orphansFile), + keyCache: make(map[string]uint32), + offsetCache: make(map[NodeID]uint32), + } + + // Process first changeset immediately + err = c.processChangeset(reader) + if err != nil { + return nil, fmt.Errorf("failed to process initial changeset: %w", err) + } + + return c, nil +} + +func (c *Compactor) processChangeset(reader *Changeset) error { + // Compute KV offset delta for non-CompactWAL mode + kvOffsetDelta := uint32(0) + if c.kvlogWriter != nil && !c.compactWAL { + kvOffsetDelta = uint32(c.kvlogWriter.Size()) + } + + versionsData := reader.versionsData + numVersions := versionsData.Count() + leavesData := reader.leavesData + branchesData := reader.branchesData + + // flush orphan writer to ensure all orphans are written before reading + err := reader.orphanWriter.Flush() + if err != nil { + return fmt.Errorf("failed to flush orphan writer before reading orphan map: %w", err) + } + orphanMap, err := ReadOrphanMap(reader.files.orphansFile) + if err != nil { + return fmt.Errorf("failed to read orphan map: %w", err) + } + + slog.DebugContext(c.ctx, "processing changeset for compaction", "versions", numVersions) + for i := 0; i < numVersions; i++ { + verInfo := *versionsData.UnsafeItem(uint32(i)) // copy + newLeafStartIdx := uint32(0) + newLeafEndIdx := uint32(0) + leafStartOffset := verInfo.Leaves.StartOffset + leafCount := verInfo.Leaves.Count + newLeafStartOffset := uint32(c.leavesWriter.Count()) + newLeafCount := uint32(0) + // Iterate leaves + // For each leaf, check if it should be retained + for j := uint32(0); j < leafCount; j++ { + leaf := *leavesData.UnsafeItem(leafStartOffset + j) // copy + id := leaf.Id + orphanVersion := orphanMap[id] + retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion) + if !retain { + continue + } + + if orphanVersion != 0 { + c.leafOrphanCount++ + c.leafOrphanVersionTotal += uint64(orphanVersion) + } + + if newLeafStartIdx == 0 { + newLeafStartIdx = id.Index() + } + newLeafEndIdx = id.Index() + newLeafCount++ + + if c.compactWAL { + k, v, err := reader.ReadKV(id, leaf.KeyOffset) + if err != nil { + return fmt.Errorf("failed to read KV for leaf %s: %w", id, err) + } + + offset, err := c.kvlogWriter.WriteKV(k, v) + if err != nil { + return fmt.Errorf("failed to write KV for leaf %s: %w", id, err) + } + + leaf.KeyOffset = offset + c.keyCache[unsafeBytesToString(k)] = offset + } else { + // When not compacting WAL, add offset delta + leaf.KeyOffset += kvOffsetDelta + } + + err := c.leavesWriter.Append(&leaf) + if err != nil { + return fmt.Errorf("failed to append leaf %s: %w", id, err) + } + + c.offsetCache[id] = uint32(c.leavesWriter.Count()) + + if orphanVersion != 0 { + if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil { + return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err) + } + } + } + + newBranchStartIdx := uint32(0) + newBranchEndIdx := uint32(0) + branchStartOffset := verInfo.Branches.StartOffset + branchCount := verInfo.Branches.Count + newBranchStartOffset := uint32(c.branchesWriter.Count()) + newBranchCount := uint32(0) + for j := uint32(0); j < branchCount; j++ { + branch := *branchesData.UnsafeItem(branchStartOffset + j) // copy + id := branch.Id + orphanVersion := orphanMap[id] + retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion) + if !retain { + continue + } + + if orphanVersion != 0 { + c.branchOrphanCount++ + c.branchOrphanVersionTotal += uint64(orphanVersion) + } + + if newBranchStartIdx == 0 { + newBranchStartIdx = id.Index() + } + newBranchEndIdx = id.Index() + newBranchCount++ + + if newLeftOffset, ok := c.offsetCache[branch.Left]; ok { + branch.LeftOffset = newLeftOffset + } + if newRightOffset, ok := c.offsetCache[branch.Right]; ok { + branch.RightOffset = newRightOffset + } + + if c.compactWAL { + k, err := reader.ReadK(id, branch.KeyOffset) + if err != nil { + return fmt.Errorf("failed to read key for branch %s: %w", id, err) + } + offset, ok := c.keyCache[unsafeBytesToString(k)] + if !ok { + offset, err = c.kvlogWriter.WriteK(k) + } + if err != nil { + return fmt.Errorf("failed to write key for branch %s: %w", id, err) + } + branch.KeyOffset = offset + } else { + // When not compacting WAL, add offset delta + branch.KeyOffset += kvOffsetDelta + } + + err := c.branchesWriter.Append(&branch) + if err != nil { + return fmt.Errorf("failed to append branch %s: %w", id, err) + } + c.offsetCache[id] = uint32(c.branchesWriter.Count()) + + if orphanVersion != 0 { + if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil { + return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err) + } + } + } + + verInfo = VersionInfo{ + Leaves: NodeSetInfo{ + StartIndex: newLeafStartIdx, + EndIndex: newLeafEndIdx, + StartOffset: newLeafStartOffset, + Count: newLeafCount, + }, + Branches: NodeSetInfo{ + StartIndex: newBranchStartIdx, + EndIndex: newBranchEndIdx, + StartOffset: newBranchStartOffset, + Count: newBranchCount, + }, + RootID: verInfo.RootID, + } + + err := c.versionsWriter.Append(&verInfo) + if err != nil { + return fmt.Errorf("failed to append version info for version %d: %w", reader.files.info.StartVersion+uint32(i), err) + } + } + + // Track this changeset as processed + c.processedChangesets = append(c.processedChangesets, reader) + + return nil +} + +func (c *Compactor) AddChangeset(cs *Changeset) error { + // TODO: Support joining changesets when CompactWAL=false + // This requires copying the entire KV log and tracking cumulative offsets + if !c.compactWAL { + return fmt.Errorf("joining changesets is only supported when CompactWAL=true") + } + return c.processChangeset(cs) +} + +func (c *Compactor) Seal() (*Changeset, error) { + if len(c.processedChangesets) == 0 { + return nil, fmt.Errorf("no changesets processed") + } + + info := c.files.info + info.StartVersion = c.processedChangesets[0].files.info.StartVersion + info.EndVersion = c.processedChangesets[len(c.processedChangesets)-1].files.info.EndVersion + info.LeafOrphans = c.leafOrphanCount + info.BranchOrphans = c.branchOrphanCount + info.LeafOrphanVersionTotal = c.leafOrphanVersionTotal + info.BranchOrphanVersionTotal = c.branchOrphanVersionTotal + + errs := []error{ + c.leavesWriter.Flush(), + c.branchesWriter.Flush(), + c.versionsWriter.Flush(), + c.orphanWriter.Flush(), + c.files.RewriteInfo(), + } + if c.kvlogWriter != nil { + errs = append(errs, c.kvlogWriter.Flush()) + } + if err := errors.Join(errs...); err != nil { + return nil, fmt.Errorf("failed to flush data during compaction seal: %w", err) + } + if err := c.files.MarkReady(); err != nil { + return nil, fmt.Errorf("failed to mark changeset as ready during compaction seal: %w", err) + } + + cs := NewChangeset(c.treeStore) + err := cs.InitOwned(c.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize sealed changeset: %w", err) + } + + // write orphan map + if err != nil { + return nil, fmt.Errorf("failed to write orphan map during compaction seal: %w", err) + } + + return cs, nil +} + +func (c *Compactor) Abort() error { + err := c.files.Close() + if err != nil { + return fmt.Errorf("failed to close compactor files during cleanup: %w", err) + } + return c.files.DeleteFiles(ChangesetDeleteArgs{ + SaveKVLogPath: c.originalKvLogPath, + }) +} + +func (c *Compactor) TotalBytes() int { + total := c.leavesWriter.Size() + c.branchesWriter.Size() + c.versionsWriter.Size() + if c.kvlogWriter != nil { + total += c.kvlogWriter.Size() + } + return total +} diff --git a/iavlx/dot_graph.go b/iavlx/dot_graph.go new file mode 100644 index 000000000000..3b0086e278be --- /dev/null +++ b/iavlx/dot_graph.go @@ -0,0 +1,239 @@ +package iavlx + +import ( + "fmt" + "io" +) + +func DebugTraverseNode(nodePtr *NodePointer, onNode func(node, parent Node, direction string) error) error { + if nodePtr == nil { + return nil + } + + var traverse func(np *NodePointer, parent Node, direction string) error + traverse = func(np *NodePointer, parent Node, direction string) error { + node, err := np.Resolve() + if err != nil { + return err + } + + if err := onNode(node, parent, direction); err != nil { + return err + } + + if node.IsLeaf() { + return nil + } + + err = traverse(node.Left(), node, "l") + if err != nil { + return err + } + err = traverse(node.Right(), node, "r") + if err != nil { + return err + } + return nil + } + + return traverse(nodePtr, nil, "") +} + +var graphvizFillColors = []string{"purple", "green", "red", "blue", "yellow"} +var graphvizTextColors = []string{"white", "black", "white", "white", "black"} + +func RenderNodeDotGraph(writer io.Writer, nodePtr *NodePointer) error { + _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=BT") + if err != nil { + return err + } + finishGraph := func() error { + _, err := fmt.Fprintln(writer, "}") + return err + } + if nodePtr == nil { + return finishGraph() + } + + err = DebugTraverseNode(nodePtr, func(node, parent Node, direction string) error { + key, err := node.Key() + if err != nil { + return err + } + + version := node.Version() + id := node.ID() + + idx := id.Index() + label := fmt.Sprintf("ver: %d idx: %d key:0x%x ", version, idx, key) + attrs := "" + fillColor := graphvizFillColors[version%uint32(len(graphvizFillColors))] + textColor := graphvizTextColors[version%uint32(len(graphvizTextColors))] + attrs += fmt.Sprintf(" fillcolor=%s fontcolor=%s style=filled", fillColor, textColor) + if node.IsLeaf() { + value, err := node.Value() + if err != nil { + return err + } + + label += fmt.Sprintf("val:0x%X", value) + attrs += " shape=box" + } else { + label += fmt.Sprintf("ht:%d sz:%d", node.Height(), node.Size()) + } + + nodeName := graphvizNodeID(id) + + _, err = fmt.Fprintf(writer, "\t%s [id=%s label=\"%s\"%s];\n", nodeName, nodeName, label, attrs) + if err != nil { + return err + } + if parent != nil { + parentName := graphvizNodeID(parent.ID()) + _, err = fmt.Fprintf(writer, "\t%s -> %s [label=\"%s\"];\n", parentName, nodeName, direction) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + + return finishGraph() +} + +func RenderChangesetDotGraph(writer io.Writer, cs *Changeset, orphans map[NodeID]uint32) error { + _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=LR") + if err != nil { + return err + } + finishGraph := func() error { + _, err := fmt.Fprintln(writer, "}") + return err + } + + _, err = fmt.Fprintln(writer, "\tsubgraph cluster_branches {\t\nlabel=\"Branches\"") + if err != nil { + return err + } + + numBranches := cs.branchesData.Count() + curVersion := uint64(0) + var lastBranchId NodeID + for i := 0; i < numBranches; i++ { + branchLayout := cs.branchesData.UnsafeItem(uint32(i)) + id := branchLayout.ID() + nodeVersion := id.Version() + if nodeVersion != curVersion { + if curVersion != 0 { + _, err = fmt.Fprintln(writer, "\t}") + } + fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))] + textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))] + _, err = fmt.Fprintf(writer, "\tsubgraph cluster_B%d {\n\t\tlabel=\"Version %d\" color=%s style=filled fontcolor=%s node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor) + } + curVersion = nodeVersion + if lastBranchId != 0 { + _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastBranchId), graphvizNodeID(id)) + } + lastBranchId = id + + nodeName := graphvizNodeID(id) + idx := id.Index() + label := fmt.Sprintf("idx: %d", idx) + orphanVersion, isOrphan := orphans[id] + if isOrphan { + label += fmt.Sprintf("
orphaned: %d", orphanVersion) + } + attrs := "" + if isOrphan { + attrs = " style=dashed" + } + vi, err := cs.getVersionInfo(uint32(nodeVersion)) + if err != nil { + return err + } + if vi.RootID == id { + attrs += " shape=doublecircle" + } + _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s>%s];\n", nodeName, nodeName, label, attrs) + if err != nil { + return err + } + } + // finish last version subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + // finish branches subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + _, err = fmt.Fprintln(writer, "\tsubgraph cluster_leaves {\t\nlabel=\"Leaves\"") + if err != nil { + return err + } + numLeaves := cs.leavesData.Count() + curVersion = 0 + var lastLeafId NodeID + for i := 0; i < numLeaves; i++ { + leafLayout := cs.leavesData.UnsafeItem(uint32(i)) + id := leafLayout.ID() + nodeVersion := id.Version() + if nodeVersion != curVersion { + if curVersion != 0 { + _, err = fmt.Fprintln(writer, "\t}") + } + fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))] + textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))] + _, err = fmt.Fprintf(writer, "\tsubgraph cluster_L%d {\n\t\tlabel=\"Version %d\" color=%s fontcolor=%s style=filled node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor) + } + curVersion = nodeVersion + if lastLeafId != 0 { + _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastLeafId), graphvizNodeID(id)) + } + lastLeafId = id + + nodeName := graphvizNodeID(id) + label := fmt.Sprintf("idx: %d", id.Index()) + orphanVersion, isOrphan := orphans[id] + if isOrphan { + label += fmt.Sprintf("
orphaned: %d", orphanVersion) + } + attrs := "" + if isOrphan { + attrs = " style=dashed" + } + _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s> shape=box%s];\n", nodeName, nodeName, label, attrs) + if err != nil { + return err + } + } + // finish last version subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + // finish leaves subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + return finishGraph() +} + +func graphvizNodeID(node NodeID) string { + if node.IsLeaf() { + return fmt.Sprintf("L%d_%d", node.Version(), node.Index()) + } else { + return fmt.Sprintf("B%d_%d", node.Version(), node.Index()) + } +} diff --git a/iavlx/immutable_tree.go b/iavlx/immutable_tree.go new file mode 100644 index 000000000000..77c01c4cb3c8 --- /dev/null +++ b/iavlx/immutable_tree.go @@ -0,0 +1,73 @@ +package iavlx + +import ( + io "io" + + storetypes "cosmossdk.io/store/types" +) + +type ImmutableTree struct { + root *NodePointer +} + +func NewImmutableTree(root *NodePointer) *ImmutableTree { + return &ImmutableTree{ + root: root, + } +} + +func (tree *ImmutableTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +func (tree *ImmutableTree) CacheWrap() storetypes.CacheWrap { + return NewCacheTree(tree) +} + +func (tree *ImmutableTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO support tracing + return tree.CacheWrap() +} + +func (tree *ImmutableTree) Get(key []byte) []byte { + if tree.root == nil { + return nil + } + + root, err := tree.root.Resolve() + if err != nil { + panic(err) + } + + value, _, err := root.Get(key) + if err != nil { + panic(err) + } + + return value +} + +func (tree *ImmutableTree) Set(key, value []byte) { + panic("cannot set in immutable tree") +} + +func (tree *ImmutableTree) Delete(key []byte) { + panic("cannot delete from immutable tree") +} + +func (tree *ImmutableTree) Has(key []byte) bool { + val := tree.Get(key) + return val != nil +} + +func (tree *ImmutableTree) Iterator(start, end []byte) storetypes.Iterator { + return NewIterator(start, end, true, tree.root, true) +} + +func (tree *ImmutableTree) ReverseIterator(start, end []byte) storetypes.Iterator { + return NewIterator(start, end, false, tree.root, true) +} + +var ( + _ storetypes.KVStore = (*ImmutableTree)(nil) +) diff --git a/iavlx/internal/btree.go b/iavlx/internal/btree.go new file mode 100644 index 000000000000..8f0b78b197ed --- /dev/null +++ b/iavlx/internal/btree.go @@ -0,0 +1,106 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/tidwall/btree" + + "cosmossdk.io/store/types" +) + +const ( + // The approximate number of items and children per B-tree node. Tuned with benchmarks. + // copied from memdb. + bTreeDegree = 32 +) + +var errKeyEmpty = errors.New("key cannot be empty") + +// BTree implements the sorted cache for cachekv store, +// we don't use MemDB here because cachekv is used extensively in sdk core path, +// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests. +// +// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly. +type BTree struct { + tree *btree.BTreeG[item] +} + +// NewBTree creates a wrapper around `btree.BTreeG`. +func NewBTree() BTree { + return BTree{ + tree: btree.NewBTreeGOptions(byKeys, btree.Options{ + Degree: bTreeDegree, + NoLocks: false, + }), + } +} + +func (bt BTree) Set(key, value []byte) { + bt.tree.Set(newItem(key, value, true)) +} + +func (bt BTree) SetCached(key, value []byte) { + bt.tree.Set(newItem(key, value, false)) +} + +func (bt BTree) Get(key []byte) ([]byte, bool) { + i, found := bt.tree.Get(newItem(key, nil, false)) + if !found { + return nil, false + } + return i.value, true +} + +func (bt BTree) Delete(key []byte) { + bt.tree.Set(newItem(key, nil, true)) +} + +func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, true), nil +} + +func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, false), nil +} + +func (bt BTree) Scan(f func(key []byte, value []byte, dirty bool) bool) { + bt.tree.Scan(func(item item) bool { + return f(item.key, item.value, item.dirty) + }) +} + +// Copy the tree. This is a copy-on-write operation and is very fast because +// it only performs a shadowed copy. +func (bt BTree) Copy() BTree { + return BTree{ + tree: bt.tree.Copy(), + } +} + +func (bt BTree) Clear() { + bt.tree.Clear() +} + +// item is a btree item with byte slices as keys and values +type item struct { + key []byte + value []byte + dirty bool +} + +// byKeys compares the items by key +func byKeys(a, b item) bool { + return bytes.Compare(a.key, b.key) == -1 +} + +// newItem creates a new pair item. +func newItem(key, value []byte, dirty bool) item { + return item{key: key, value: value, dirty: dirty} +} diff --git a/iavlx/internal/btree_test.go b/iavlx/internal/btree_test.go new file mode 100644 index 000000000000..50fa74c56a74 --- /dev/null +++ b/iavlx/internal/btree_test.go @@ -0,0 +1,204 @@ +package internal + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/store/types" +) + +func TestGetSetDelete(t *testing.T) { + db := NewBTree() + + // A nonexistent key should return nil. + value, _ := db.Get([]byte("a")) + require.Nil(t, value) + + // Set and get a value. + db.Set([]byte("a"), []byte{0x01}) + db.Set([]byte("b"), []byte{0x02}) + value, _ = db.Get([]byte("a")) + require.Equal(t, []byte{0x01}, value) + + value, _ = db.Get([]byte("b")) + require.Equal(t, []byte{0x02}, value) + + // Deleting a non-existent value is fine. + db.Delete([]byte("x")) + + // Delete a value. + db.Delete([]byte("a")) + + value, _ = db.Get([]byte("a")) + require.Nil(t, value) + + db.Delete([]byte("b")) + + value, _ = db.Get([]byte("b")) + require.Nil(t, value) +} + +func TestDBIterator(t *testing.T) { + db := NewBTree() + + for i := 0; i < 10; i++ { + if i != 6 { // but skip 6. + db.Set(int642Bytes(int64(i)), []byte{}) + } + } + + // Blank iterator keys should error + _, err := db.ReverseIterator([]byte{}, nil) + require.Equal(t, errKeyEmpty, err) + _, err = db.ReverseIterator(nil, []byte{}) + require.Equal(t, errKeyEmpty, err) + + itr, err := db.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") + + ritr, err := db.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") + + itr, err = db.Iterator(nil, int642Bytes(0)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator to 0") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)") + + itr, err = db.Iterator(int642Bytes(0), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") + + itr, err = db.Iterator(int642Bytes(1), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") + + ritr, err = db.ReverseIterator(nil, int642Bytes(10)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8") + + itr, err = db.Iterator(int642Bytes(7), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 6 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 7 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 7 (ex) to 6") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10") + + ritr, err = db.ReverseIterator(int642Bytes(6), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6") + + ritr, err = db.ReverseIterator(int642Bytes(5), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5") + + ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8") + + ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{3, 2}, "reverse iterator from 4 (ex) to 2") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 2 (ex) to 4") + + // Ensure that the iterators don't panic with an empty database. + db2 := NewBTree() + + itr, err = db2.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, nil, "forward iterator with empty db") + + ritr, err = db2.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, nil, "reverse iterator with empty db") +} + +func verifyIterator(t *testing.T, itr types.Iterator, expected []int64, msg string) { + t.Helper() + i := 0 + for itr.Valid() { + key := itr.Key() + require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i) + itr.Next() + i++ + } + require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter") + require.NoError(t, itr.Close()) +} + +func int642Bytes(i int64) []byte { + return types.Uint64ToBigEndian(uint64(i)) +} + +func bytes2Int64(buf []byte) int64 { + return int64(types.BigEndianToUint64(buf)) +} diff --git a/iavlx/internal/memiterator.go b/iavlx/internal/memiterator.go new file mode 100644 index 000000000000..ab7e6e13a6ba --- /dev/null +++ b/iavlx/internal/memiterator.go @@ -0,0 +1,120 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/tidwall/btree" + + "cosmossdk.io/store/types" +) + +var _ types.Iterator = (*memIterator)(nil) + +// memIterator iterates over iterKVCache items. +// if value is nil, means it was deleted. +// Implements Iterator. +type memIterator struct { + iter btree.IterG[item] + + start []byte + end []byte + ascending bool + valid bool +} + +func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { + iter := items.tree.Iter() + var valid bool + if ascending { + if start != nil { + valid = iter.Seek(newItem(start, nil, false)) + } else { + valid = iter.First() + } + } else { + if end != nil { + valid = iter.Seek(newItem(end, nil, false)) + if !valid { + valid = iter.Last() + } else { + // end is exclusive + valid = iter.Prev() + } + } else { + valid = iter.Last() + } + } + + mi := &memIterator{ + iter: iter, + start: start, + end: end, + ascending: ascending, + valid: valid, + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } + + return mi +} + +func (mi *memIterator) Domain() (start, end []byte) { + return mi.start, mi.end +} + +func (mi *memIterator) Close() error { + mi.iter.Release() + return nil +} + +func (mi *memIterator) Error() error { + if !mi.Valid() { + return errors.New("invalid memIterator") + } + return nil +} + +func (mi *memIterator) Valid() bool { + return mi.valid +} + +func (mi *memIterator) Next() { + mi.assertValid() + + if mi.ascending { + mi.valid = mi.iter.Next() + } else { + mi.valid = mi.iter.Prev() + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } +} + +func (mi *memIterator) keyInRange(key []byte) bool { + if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { + return false + } + if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { + return false + } + return true +} + +func (mi *memIterator) Key() []byte { + return mi.iter.Item().key +} + +func (mi *memIterator) Value() []byte { + return mi.iter.Item().value +} + +func (mi *memIterator) assertValid() { + if err := mi.Error(); err != nil { + panic(err) + } +} diff --git a/iavlx/internal/mergeiterator.go b/iavlx/internal/mergeiterator.go new file mode 100644 index 000000000000..15b215841931 --- /dev/null +++ b/iavlx/internal/mergeiterator.go @@ -0,0 +1,235 @@ +package internal + +import ( + "bytes" + "errors" + + "cosmossdk.io/store/types" +) + +// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// The cache iterator may return nil keys to signal that an item +// had been deleted (but not deleted in the parent). +// If the cache iterator has the same key as the parent, the +// cache shadows (overrides) the parent. +// +// TODO: Optimize by memoizing. +type cacheMergeIterator struct { + parent types.Iterator + cache types.Iterator + ascending bool + + valid bool +} + +var _ types.Iterator = (*cacheMergeIterator)(nil) + +func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.Iterator { + iter := &cacheMergeIterator{ + parent: parent, + cache: cache, + ascending: ascending, + } + + iter.valid = iter.skipUntilExistsOrInvalid() + return iter +} + +// Domain implements Iterator. +// Returns parent domain because cache and parent domains are the same. +func (iter *cacheMergeIterator) Domain() (start, end []byte) { + return iter.parent.Domain() +} + +// Valid implements Iterator. +func (iter *cacheMergeIterator) Valid() bool { + return iter.valid +} + +// Next implements Iterator +func (iter *cacheMergeIterator) Next() { + iter.assertValid() + + switch { + case !iter.parent.Valid(): + // If parent is invalid, get the next cache item. + iter.cache.Next() + case !iter.cache.Valid(): + // If cache is invalid, get the next parent item. + iter.parent.Next() + default: + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + switch iter.compare(keyP, keyC) { + case -1: // parent < cache + iter.parent.Next() + case 0: // parent == cache + iter.parent.Next() + iter.cache.Next() + case 1: // parent > cache + iter.cache.Next() + } + } + iter.valid = iter.skipUntilExistsOrInvalid() +} + +// Key implements Iterator +func (iter *cacheMergeIterator) Key() []byte { + iter.assertValid() + + // If parent is invalid, get the cache key. + if !iter.parent.Valid() { + return iter.cache.Key() + } + + // If cache is invalid, get the parent key. + if !iter.cache.Valid() { + return iter.parent.Key() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return keyP + case 0: // parent == cache + return keyP + case 1: // parent > cache + return keyC + default: + panic("invalid compare result") + } +} + +// Value implements Iterator +func (iter *cacheMergeIterator) Value() []byte { + iter.assertValid() + + // If parent is invalid, get the cache value. + if !iter.parent.Valid() { + return iter.cache.Value() + } + + // If cache is invalid, get the parent value. + if !iter.cache.Valid() { + return iter.parent.Value() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return iter.parent.Value() + case 0: // parent == cache + return iter.cache.Value() + case 1: // parent > cache + return iter.cache.Value() + default: + panic("invalid comparison result") + } +} + +// Close implements Iterator +func (iter *cacheMergeIterator) Close() error { + err1 := iter.cache.Close() + if err := iter.parent.Close(); err != nil { + return err + } + + return err1 +} + +// Error returns an error if the cacheMergeIterator is invalid defined by the +// Valid method. +func (iter *cacheMergeIterator) Error() error { + if !iter.Valid() { + return errors.New("invalid cacheMergeIterator") + } + + return nil +} + +// assertValid checks if not valid, panics. +// NOTE: May have side-effect of iterating over cache. +func (iter *cacheMergeIterator) assertValid() { + if err := iter.Error(); err != nil { + panic(err) + } +} + +// compare is like bytes.Compare but opposite if not ascending. +func (iter *cacheMergeIterator) compare(a, b []byte) int { + if iter.ascending { + return bytes.Compare(a, b) + } + + return bytes.Compare(a, b) * -1 +} + +// skipCacheDeletes skips all delete-items from the cache w/ `key < until`. After this function, +// current cache item is a non-delete-item, or `until <= key`. +// If the current cache item is not a delete item, does nothing. +// If `until` is nil, there is no limit, and cache may end up invalid. +// CONTRACT: cache is valid. +func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { + for iter.cache.Valid() && + iter.cache.Value() == nil && + (until == nil || iter.compare(iter.cache.Key(), until) < 0) { + iter.cache.Next() + } +} + +// skipUntilExistsOrInvalid fast forwards cache (or parent+cache in case of deleted items) until current +// item exists, or until iterator becomes invalid. +// Returns whether the iterator is valid. +func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { + for { + // If parent is invalid, fast-forward cache. + if !iter.parent.Valid() { + iter.skipCacheDeletes(nil) + return iter.cache.Valid() + } + // Parent is valid. + + if !iter.cache.Valid() { + return true + } + // Parent is valid, cache is valid. + + // Compare parent and cache. + keyP := iter.parent.Key() + keyC := iter.cache.Key() + + switch iter.compare(keyP, keyC) { + case -1: // parent < cache. + return true + + case 0: // parent == cache. + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.parent.Next() + iter.cache.Next() + + continue + } + // Cache is not a delete. + + return true // cache exists. + case 1: // cache < parent + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.skipCacheDeletes(keyP) + continue + } + // Cache is not a delete. + + return true // cache exists. + } + } +} diff --git a/iavlx/iterator.go b/iavlx/iterator.go new file mode 100644 index 000000000000..111c0f9c9352 --- /dev/null +++ b/iavlx/iterator.go @@ -0,0 +1,139 @@ +package iavlx + +import "bytes" + +type Iterator struct { + // domain of iteration, end is exclusive + start, end []byte + ascending bool + zeroCopy bool + + // cache the next key-value pair + key, value []byte + + err error + valid bool + + stack []*NodePointer +} + +func NewIterator(start, end []byte, ascending bool, root *NodePointer, zeroCopy bool) *Iterator { + iter := &Iterator{ + start: start, + end: end, + ascending: ascending, + valid: true, + zeroCopy: zeroCopy, + } + + if root != nil { + iter.stack = []*NodePointer{root} + } + + // cache the first key-value + iter.Next() + return iter +} + +func (iter *Iterator) Domain() ([]byte, []byte) { + return iter.start, iter.end +} + +// Valid implements dbm.Iterator. +func (iter *Iterator) Valid() bool { + return iter.valid +} + +// Error implements dbm.Iterator +func (iter *Iterator) Error() error { + return nil +} + +// Key implements dbm.Iterator +func (iter *Iterator) Key() []byte { + if !iter.zeroCopy { + return bytes.Clone(iter.key) + } + return iter.key +} + +// Value implements dbm.Iterator +func (iter *Iterator) Value() []byte { + if !iter.zeroCopy { + return bytes.Clone(iter.value) + } + return iter.value +} + +// Next implements dbm.Iterator +func (iter *Iterator) Next() { + if !iter.valid { + return + } + + for len(iter.stack) > 0 { + // pop node + nodePtr := iter.stack[len(iter.stack)-1] + iter.stack = iter.stack[:len(iter.stack)-1] + + node, err := nodePtr.Resolve() + if err != nil { + iter.fail(err) + return + } + + key, err := node.Key() + if err != nil { + iter.fail(err) + return + } + startCmp := bytes.Compare(iter.start, key) + afterStart := iter.start == nil || startCmp < 0 + beforeEnd := iter.end == nil || bytes.Compare(key, iter.end) < 0 + + if node.IsLeaf() { + startOrAfter := afterStart || startCmp == 0 + if startOrAfter && beforeEnd { + iter.key = key + value, err := node.Value() + if err != nil { + iter.fail(err) + return + } + iter.value = value + return + } + } else { + // push children to stack + if iter.ascending { + if beforeEnd { + iter.stack = append(iter.stack, node.Right()) + } + if afterStart { + iter.stack = append(iter.stack, node.Left()) + } + } else { + if afterStart { + iter.stack = append(iter.stack, node.Left()) + } + if beforeEnd { + iter.stack = append(iter.stack, node.Right()) + } + } + } + } + + iter.valid = false +} + +func (iter *Iterator) fail(err error) { + iter.valid = false + iter.err = err +} + +// Close implements dbm.Iterator +func (iter *Iterator) Close() error { + iter.valid = false + iter.stack = nil + return nil +} diff --git a/iavlx/kvlog.go b/iavlx/kvlog.go new file mode 100644 index 000000000000..afdaa89ae7fe --- /dev/null +++ b/iavlx/kvlog.go @@ -0,0 +1,51 @@ +package iavlx + +import ( + "encoding/binary" + "os" +) + +const ( + KVLogEntryTypeSet byte = iota + KVLogEntryTypeDelete + KVLogEntryTypeCommit + KVLogEntryTypeExtraK + KVLogEntryTypeExtraKV +) + +type KVLog struct { + *MmapFile +} + +func NewKVLog(file *os.File) (*KVLog, error) { + mmap, err := NewMmapFile(file) + if err != nil { + return nil, err + } + return &KVLog{ + MmapFile: mmap, + }, nil +} + +func (kvs *KVLog) UnsafeReadK(offset uint32) (key []byte, err error) { + bz, err := kvs.UnsafeSliceExact(int(offset), 4) + if err != nil { + return nil, err + } + lenKey := binary.LittleEndian.Uint32(bz) + + return kvs.UnsafeSliceExact(int(offset)+4, int(lenKey)) +} + +func (kvs *KVLog) UnsafeReadKV(offset uint32) (key, value []byte, err error) { + key, err = kvs.UnsafeReadK(offset) + if err != nil { + return nil, nil, err + } + + value, err = kvs.UnsafeReadK(offset + 4 + uint32(len(key))) + if err != nil { + return nil, nil, err + } + return key, value, nil +} diff --git a/iavlx/kvlog_writer.go b/iavlx/kvlog_writer.go new file mode 100644 index 000000000000..19bb3083634d --- /dev/null +++ b/iavlx/kvlog_writer.go @@ -0,0 +1,114 @@ +package iavlx + +import ( + "encoding/binary" + "fmt" + "math" + "os" +) + +type KVLogWriter struct { + *FileWriter +} + +func NewKVDataWriter(file *os.File) *KVLogWriter { + fw := NewFileWriter(file) + return &KVLogWriter{ + FileWriter: fw, + } +} + +func (kvs *KVLogWriter) WriteK(key []byte) (offset uint32, err error) { + _, err = kvs.Write([]byte{KVLogEntryTypeExtraK}) + if err != nil { + return offset, err + } + + return kvs.writeLenPrefixedBytes(key) +} + +func (kvs *KVLogWriter) WriteKV(key, value []byte) (offset uint32, err error) { + _, err = kvs.Write([]byte{KVLogEntryTypeExtraKV}) + if err != nil { + return offset, err + } + + offset, err = kvs.writeLenPrefixedBytes(key) + if err != nil { + return 0, err + } + _, err = kvs.writeLenPrefixedBytes(value) + return offset, err +} + +func (kvs *KVLogWriter) WriteUpdates(updates []KVUpdate) error { + for _, update := range updates { + if deleteKey := update.DeleteKey; deleteKey != nil { + _, err := kvs.Write([]byte{KVLogEntryTypeDelete}) + if err != nil { + return err + } + _, err = kvs.writeLenPrefixedBytes(deleteKey) + if err != nil { + return err + } + } else if memNode := update.SetNode; memNode != nil { + _, err := kvs.Write([]byte{KVLogEntryTypeSet}) + if err != nil { + return err + } + offset, err := kvs.writeLenPrefixedBytes(memNode.key) + if err != nil { + return err + } + memNode.keyOffset = offset + + _, err = kvs.writeLenPrefixedBytes(memNode.value) + if err != nil { + return err + } + } else { + return fmt.Errorf("invalid update: neither SetNode nor DeleteKey is set") + } + } + return nil +} + +func (kvs *KVLogWriter) WriteCommit(version uint32) error { + _, err := kvs.Write([]byte{KVLogEntryTypeCommit}) + if err != nil { + return err + } + + return kvs.writeLEU32(version) +} + +func (kvs *KVLogWriter) writeLenPrefixedBytes(key []byte) (offset uint32, err error) { + lenKey := len(key) + if lenKey > math.MaxUint32 { + return 0, fmt.Errorf("key too large: %d bytes", lenKey) + } + + offset = uint32(kvs.Size()) + + // write little endian uint32 length prefix + err = kvs.writeLEU32(uint32(lenKey)) + if err != nil { + return offset, err + } + + // write key bytes + _, err = kvs.Write(key) + if err != nil { + return offset, err + } + + return offset, nil +} + +func (kvs *KVLogWriter) writeLEU32(x uint32) error { + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], x) + _, err := kvs.Write(buf[:]) + return err +} diff --git a/iavlx/leaf_layout.go b/iavlx/leaf_layout.go new file mode 100644 index 000000000000..11f90311e1d5 --- /dev/null +++ b/iavlx/leaf_layout.go @@ -0,0 +1,26 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(LeafLayout{}) != SizeLeaf { + panic(fmt.Sprintf("invalid LeafLayout size: got %d, want %d", unsafe.Sizeof(LeafLayout{}), SizeLeaf)) + } +} + +const ( + SizeLeaf = 48 +) + +type LeafLayout struct { + Id NodeID + Hash [32]byte + KeyOffset uint32 // TODO check if we have extra padding here +} + +func (l LeafLayout) ID() NodeID { + return l.Id +} diff --git a/iavlx/leaf_persisted.go b/iavlx/leaf_persisted.go new file mode 100644 index 000000000000..8ebb1a5f9385 --- /dev/null +++ b/iavlx/leaf_persisted.go @@ -0,0 +1,87 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +type LeafPersisted struct { + store *Changeset + selfIdx uint32 + layout LeafLayout +} + +func (node *LeafPersisted) ID() NodeID { + return node.layout.Id +} + +func (node *LeafPersisted) Height() uint8 { + return 0 +} + +func (node *LeafPersisted) IsLeaf() bool { + return true +} + +func (node *LeafPersisted) Size() int64 { + return 1 +} + +func (node *LeafPersisted) Version() uint32 { + return uint32(node.layout.Id.Version()) +} + +func (node *LeafPersisted) Key() ([]byte, error) { + return node.store.ReadK(node.layout.Id, node.layout.KeyOffset) +} + +func (node *LeafPersisted) Value() ([]byte, error) { + return node.store.ReadV(node.layout.Id, node.layout.KeyOffset) +} + +func (node *LeafPersisted) Left() *NodePointer { + return nil +} + +func (node *LeafPersisted) Right() *NodePointer { + return nil +} + +func (node *LeafPersisted) Hash() []byte { + return node.layout.Hash[:] +} + +func (node *LeafPersisted) SafeHash() []byte { + // TODO how do we make this safe? + return node.layout.Hash[:] +} + +func (node *LeafPersisted) MutateBranch(uint32) (*MemNode, error) { + return nil, fmt.Errorf("leaf nodes should not get mutated this way") +} + +func (node *LeafPersisted) Get(key []byte) (value []byte, index int64, err error) { + nodeKey, err := node.Key() + if err != nil { + return nil, 0, err + } + switch bytes.Compare(nodeKey, key) { + case -1: + return nil, 1, nil + case 1: + return nil, 0, nil + default: + value, err := node.Value() + if err != nil { + return nil, 0, err + } + return value, 0, nil + } +} + +func (node *LeafPersisted) String() string { + // TODO implement me + panic("implement me") +} + +var _ Node = (*LeafPersisted)(nil) diff --git a/iavlx/load.go b/iavlx/load.go new file mode 100644 index 000000000000..6adc19d016d7 --- /dev/null +++ b/iavlx/load.go @@ -0,0 +1,111 @@ +package iavlx + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/tidwall/btree" +) + +func (ts *TreeStore) load() error { + // collect a list of all existing subdirectories + dirs, err := os.ReadDir(ts.dir) + if err != nil { + return fmt.Errorf("failed to read tree store dir: %w", err) + } + + // directory map: startVersion -> compactedAt -> dirName + var dirMap btree.Map[uint64, *btree.Map[uint64, string]] + for _, de := range dirs { + if !de.IsDir() { + continue + } + + dirName := de.Name() + startVersion, compactedAt, valid := ParseChangesetDirName(dirName) + if !valid { + continue + } + + dir := filepath.Join(ts.dir, dirName) + if _, found := dirMap.Get(startVersion); !found { + dirMap.Set(startVersion, &btree.Map[uint64, string]{}) + } + caMap, _ := dirMap.Get(startVersion) + caMap.Set(compactedAt, dir) + } + + // load changesets in order + for { + startVersion, compactionMap, ok := dirMap.PopMin() + if !ok { + return nil + } + + // startVersion should be equal to stagedVersion + + if startVersion < uint64(ts.stagedVersion) { + ts.logger.Warn("found undeleted changeset that was already compacted", "startVersion", startVersion, "stagedVersion", ts.stagedVersion) + // TODO delete undeleted compactions + continue + } + + if startVersion > uint64(ts.stagedVersion) { + return fmt.Errorf("missing changeset for staged version %d", ts.stagedVersion) + } + + for { + _, dirName, ok := compactionMap.PopMax() + if !ok { + return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion) + } + + ready, err := IsChangesetReady(dirName) + if err != nil { + return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err) + } + + if !ready { + ts.logger.Warn("found incomplete compaction, deleting", "dir", dirName) + err := os.RemoveAll(dirName) + if err != nil { + ts.logger.Error("failed to remove incomplete compaction", "dir", dirName, "error", err) + } + continue + } + + ts.logger.Debug("loading changeset", "startVersion", startVersion, "dir", dirName) + + cs, err := OpenChangeset(ts, dirName) + if err != nil { + return fmt.Errorf("failed to open changeset in %s: %w", dirName, err) + } + + realStartVersion := cs.info.StartVersion + if uint64(realStartVersion) != startVersion { + if realStartVersion == 0 { + if dirMap.Len() != 0 { + return fmt.Errorf("found incomplete changeset %s, but there are later changesets present", dirName) + } + ts.logger.Debug("found final incomplete changeset, deleting", "dir", dirName) + err := os.RemoveAll(dirName) + if err != nil { + return fmt.Errorf("failed to remove incomplete changeset %s: %w", dirName, err) + } + break + } else { + return fmt.Errorf("changeset in %s has mismatched start version %d (expected %d)", dirName, realStartVersion, startVersion) + } + } + + ce := &changesetEntry{} + ce.changeset.Store(cs) + ts.changesets.Set(uint32(startVersion), ce) + + ts.savedVersion.Store(cs.info.EndVersion) + ts.stagedVersion = cs.info.EndVersion + 1 + break + } + } +} diff --git a/iavlx/mem_node.go b/iavlx/mem_node.go new file mode 100644 index 000000000000..a8bb4a0168db --- /dev/null +++ b/iavlx/mem_node.go @@ -0,0 +1,116 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +type MemNode struct { + height uint8 + size int64 + version uint32 + key []byte + value []byte + left *NodePointer + right *NodePointer + hash []byte + nodeId NodeID // ID of this node, 0 if not yet assigned + keyOffset uint32 +} + +func (node *MemNode) ID() NodeID { + return node.nodeId +} + +func (node *MemNode) Height() uint8 { + return node.height +} + +func (node *MemNode) Size() int64 { + return node.size +} + +func (node *MemNode) Version() uint32 { + return node.version +} + +func (node *MemNode) Key() ([]byte, error) { + return node.key, nil +} + +func (node *MemNode) Value() ([]byte, error) { + return node.value, nil +} + +func (node *MemNode) Left() *NodePointer { + return node.left +} + +func (node *MemNode) Right() *NodePointer { + return node.right +} + +func (node *MemNode) Hash() []byte { + return node.hash +} + +func (node *MemNode) SafeHash() []byte { + // TODO what needs to be safe?? + return node.hash +} + +func (node *MemNode) MutateBranch(version uint32) (*MemNode, error) { + n := *node + n.version = version + n.hash = nil + return &n, nil +} + +func (node *MemNode) Get(key []byte) (value []byte, index int64, err error) { + if node.IsLeaf() { + switch bytes.Compare(node.key, key) { + case -1: + return nil, 1, nil + case 1: + return nil, 0, nil + default: + return node.value, 0, nil + } + } + + if bytes.Compare(key, node.key) < 0 { + leftNode, err := node.left.Resolve() + if err != nil { + return nil, 0, err + } + + return leftNode.Get(key) + } + + rightNode, err := node.right.Resolve() + if err != nil { + return nil, 0, err + } + + value, index, err = rightNode.Get(key) + if err != nil { + return nil, 0, err + } + + index += node.size - rightNode.Size() + return value, index, nil +} + +func (node *MemNode) IsLeaf() bool { + return node.height == 0 +} + +func (node *MemNode) String() string { + if node.IsLeaf() { + return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, value:%x}", node.key, node.version, node.size, node.value) + } else { + return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, height:%d, left:%s, right:%s}", node.key, node.version, node.size, node.height, node.left, node.right) + } +} + +var _ Node = &MemNode{} diff --git a/iavlx/mmap.go b/iavlx/mmap.go new file mode 100644 index 000000000000..570538e0f1ae --- /dev/null +++ b/iavlx/mmap.go @@ -0,0 +1,78 @@ +package iavlx + +import ( + "fmt" + "io" + "os" + + "github.com/edsrzf/mmap-go" +) + +type MmapFile struct { + handle mmap.MMap +} + +func NewMmapFile(file *os.File) (*MmapFile, error) { + // Check file size + fi, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("failed to stat file: %w", err) + } + + res := &MmapFile{} + + // Empty files are valid - just don't mmap them + if fi.Size() == 0 { + return res, nil + } + + // maybe we can make read/write configurable? not sure if the OS optimizes read-only mapping + handle, err := mmap.Map(file, mmap.RDONLY, 0) + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("failed to mmap file: %w", err) + } + + res.handle = handle + return res, nil +} + +func (m *MmapFile) UnsafeSliceVar(offset, maxSize int) (int, []byte, error) { + if offset >= len(m.handle) { + return 0, nil, fmt.Errorf("trying to read beyond mapped data: %d >= %d", offset, len(m.handle)) + } + if offset+maxSize > len(m.handle) { + maxSize = len(m.handle) - offset + } + data := m.handle[offset : offset+maxSize] + // make a copy of the data to avoid data being changed after remap + return maxSize, data, nil +} + +func (m *MmapFile) UnsafeSliceExact(offset, size int) ([]byte, error) { + if offset+size > len(m.handle) { + return nil, fmt.Errorf("trying to read beyond mapped data: %d + %d >= %d", offset, size, len(m.handle)) + } + bz := m.handle[offset : offset+size] + return bz, nil +} + +func (m *MmapFile) Data() []byte { + return m.handle +} + +func (m *MmapFile) Close() error { + if m.handle != nil { + handle := m.handle + m.handle = nil + return handle.Unmap() + } + return nil +} + +func (m *MmapFile) TotalBytes() int { + return len(m.handle) +} + +var _ io.Closer = &MmapFile{} diff --git a/iavlx/multi_tree.go b/iavlx/multi_tree.go new file mode 100644 index 000000000000..46c3465beabb --- /dev/null +++ b/iavlx/multi_tree.go @@ -0,0 +1,129 @@ +package iavlx + +import ( + "fmt" + io "io" + "sync" + + storetypes "cosmossdk.io/store/types" +) + +type MultiTree struct { + latestVersion int64 + trees []storetypes.CacheWrap // always ordered by tree name + treesByKey map[storetypes.StoreKey]int // index of the trees by name + + parentTree func(storetypes.StoreKey) storetypes.CacheWrapper +} + +func (t *MultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore { + store, ok := t.getCacheWrapper(key).(storetypes.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + return store +} + +func (t *MultiTree) getCacheWrapper(key storetypes.StoreKey) storetypes.CacheWrapper { + var store storetypes.CacheWrapper + treeIdx, ok := t.treesByKey[key] + if !ok { + if t.parentTree != nil { + store = t.initStore(key, t.parentTree(key)) + } else { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + } else { + store = t.trees[treeIdx] + } + + return store +} + +func (t *MultiTree) initStore(key storetypes.StoreKey, store storetypes.CacheWrapper) storetypes.CacheWrap { + cache := store.CacheWrap() + t.trees = append(t.trees, cache) + t.treesByKey[key] = len(t.trees) - 1 + return cache +} + +func (t *MultiTree) Write() { + var wg sync.WaitGroup + for _, tree := range t.trees { + // TODO check if trees are dirty before spinning off a goroutine + wg.Add(1) + go func(t storetypes.CacheWrap) { + defer wg.Done() + t.Write() + }(tree) + } + wg.Wait() +} + +func (t *MultiTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti +} + +func (t *MultiTree) CacheWrap() storetypes.CacheWrap { + return t.CacheMultiStore() +} + +func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO implement tracing + return t.CacheWrap() +} + +func (t *MultiTree) CacheMultiStore() storetypes.CacheMultiStore { + return NewFromParent(t.getCacheWrapper, t.latestVersion) +} + +func NewFromParent(parentStore func(storetypes.StoreKey) storetypes.CacheWrapper, version int64) *MultiTree { + return &MultiTree{ + latestVersion: version, + parentTree: parentStore, + trees: make([]storetypes.CacheWrap, 0), + treesByKey: make(map[storetypes.StoreKey]int), + } +} + +func (t *MultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + return nil, fmt.Errorf("CacheMultiStoreWithVersion can only be called on CommitMultiStore") +} + +func (t *MultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { + store, ok := t.getCacheWrapper(key).(storetypes.Store) + if !ok { + panic(fmt.Sprintf("store with key %v is not Store", key)) + } + return store +} + +func (t *MultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + store := t.getCacheWrapper(key) + + kvStore, ok := store.(storetypes.KVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not KVStore: store type=%T", key, store)) + } + return kvStore +} + +func (t *MultiTree) TracingEnabled() bool { + return false +} + +func (t *MultiTree) SetTracer(w io.Writer) storetypes.MultiStore { + // TODO implement me + panic("implement me") +} + +func (t *MultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + // TODO implement me + panic("implement me") +} + +func (t *MultiTree) LatestVersion() int64 { + return t.latestVersion +} + +var _ storetypes.MultiStore = &MultiTree{} diff --git a/iavlx/node.go b/iavlx/node.go new file mode 100644 index 000000000000..721d2a6f22b8 --- /dev/null +++ b/iavlx/node.go @@ -0,0 +1,21 @@ +package iavlx + +import "fmt" + +type Node interface { + ID() NodeID + Height() uint8 + IsLeaf() bool + Size() int64 + Version() uint32 + Key() ([]byte, error) + Value() ([]byte, error) + Left() *NodePointer + Right() *NodePointer + Hash() []byte + SafeHash() []byte + MutateBranch(version uint32) (*MemNode, error) + Get(key []byte) (value []byte, index int64, err error) + + fmt.Stringer +} diff --git a/iavlx/node_hash.go b/iavlx/node_hash.go new file mode 100644 index 000000000000..89fadcc203c0 --- /dev/null +++ b/iavlx/node_hash.go @@ -0,0 +1,118 @@ +package iavlx + +import ( + "crypto/sha256" + "encoding/binary" + "fmt" + "hash" + "io" + "sync" +) + +func computeAndSetHash(node *MemNode, leftHash, rightHash []byte) ([]byte, error) { + h, err := computeHash(node, leftHash, rightHash) + if err != nil { + return nil, err + } + node.hash = h + + return h, nil +} + +var hasherPool = sync.Pool{ + New: func() any { + return sha256.New() + }, +} + +func putBackHasher(h hash.Hash) { + h.Reset() + hasherPool.Put(h) +} + +func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) { + hasher := hasherPool.Get().(hash.Hash) + defer putBackHasher(hasher) + if err := writeHashBytes(node, leftHash, rightHash, hasher); err != nil { + return nil, err + } + return hasher.Sum(nil), nil +} + +var emptyHash = sha256.New().Sum(nil) + +func shaSum256(bz []byte) []byte { + hasher := hasherPool.Get().(hash.Hash) + defer putBackHasher(hasher) + hasher.Write(bz) + var sum [sha256.Size]byte + hasher.Sum(sum[:0]) + return sum[:] +} + +// Writes the node's hash to the given `io.Writer`. This function recursively calls +// children to update hashes. +func writeHashBytes(node Node, leftHash, rightHash []byte, w io.Writer) error { + var ( + n int + buf [binary.MaxVarintLen64]byte + ) + + n = binary.PutVarint(buf[:], int64(node.Height())) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing height, %w", err) + } + n = binary.PutVarint(buf[:], node.Size()) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing size, %w", err) + } + n = binary.PutVarint(buf[:], int64(node.Version())) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing version, %w", err) + } + + // Key is not written for inner nodes, unlike writeBytes. + + if node.IsLeaf() { + key, err := node.Key() + if err != nil { + return fmt.Errorf("getting key, %w", err) + } + + if err := encodeVarintPrefixedBytes(w, key); err != nil { + return fmt.Errorf("writing key, %w", err) + } + + value, err := node.Value() + if err != nil { + return fmt.Errorf("getting value, %w", err) + } + + // Indirection needed to provide proofs without values. + // (e.g. ProofLeafNode.ValueHash) + if err := encodeVarintPrefixedBytes(w, shaSum256(value)); err != nil { + return fmt.Errorf("writing value, %w", err) + } + } else { + if err := encodeVarintPrefixedBytes(w, leftHash); err != nil { + return fmt.Errorf("writing left hash, %w", err) + } + if err := encodeVarintPrefixedBytes(w, rightHash); err != nil { + return fmt.Errorf("writing right hash, %w", err) + } + } + + return nil +} + +// encodeVarintPrefixedBytes writes a varint length-prefixed byte slice to the writer, +// it's used for hash computation, must be compactible with the official IAVL implementation. +func encodeVarintPrefixedBytes(w io.Writer, bz []byte) error { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], uint64(len(bz))) + if _, err := w.Write(buf[0:n]); err != nil { + return err + } + _, err := w.Write(bz) + return err +} diff --git a/iavlx/node_id.go b/iavlx/node_id.go new file mode 100644 index 000000000000..b21566a843cd --- /dev/null +++ b/iavlx/node_id.go @@ -0,0 +1,44 @@ +package iavlx + +import "fmt" + +// NodeID is a stable identifier for a node in the IAVL tree. +// Bit 63 indicates whether this is a leaf (1) or branch (0) +// Bits 62-23 (40 bits) are for version. +// Bits 22-0 (23 bits) are for index. +// Valid index values start from 1. A zero index value may be used to indicate a null node. +type NodeID uint64 + +func NewNodeID(isLeaf bool, version uint64, index uint32) NodeID { + // check 40 bits for version and 23 bits for index + if version >= 0x10000000000 { + panic("version too large for NodeID") + } + if index >= 0x800000 { + panic("index too large for NodeID") + } + var id uint64 + if isLeaf { + id |= 1 << 63 + } + id |= (version & 0xFFFFFFFFFF) << 23 + id |= uint64(index & 0x7FFFFF) + return NodeID(id) +} + +func (id NodeID) IsLeaf() bool { + // check if highest bit is set + return id&(1<<63) != 0 +} + +func (id NodeID) Version() uint64 { + return (uint64(id) >> 23) & 0xFFFFFFFFFF +} + +func (id NodeID) Index() uint32 { + return uint32(id & 0x7FFFFF) +} + +func (id NodeID) String() string { + return fmt.Sprintf("NodeID{leaf:%t, version:%d, index:%d}", id.IsLeaf(), id.Version(), id.Index()) +} diff --git a/iavlx/node_pointer.go b/iavlx/node_pointer.go new file mode 100644 index 000000000000..26cc1c568fef --- /dev/null +++ b/iavlx/node_pointer.go @@ -0,0 +1,31 @@ +package iavlx + +import ( + "fmt" + "sync/atomic" +) + +type NodePointer struct { + mem atomic.Pointer[MemNode] + store *Changeset + fileIdx uint32 // absolute index in file, 1-based, zero means we don't have an offset + id NodeID +} + +func NewNodePointer(memNode *MemNode) *NodePointer { + n := &NodePointer{} + n.mem.Store(memNode) + return n +} + +func (p *NodePointer) Resolve() (Node, error) { + mem := p.mem.Load() + if mem != nil { + return mem, nil + } + return p.store.Resolve(p.id, p.fileIdx) +} + +func (p *NodePointer) String() string { + return fmt.Sprintf("NodePointer{id: %s, fileIdx: %d}", p.id.String(), p.fileIdx) +} diff --git a/iavlx/node_update.go b/iavlx/node_update.go new file mode 100644 index 000000000000..dd2000860bd3 --- /dev/null +++ b/iavlx/node_update.go @@ -0,0 +1,357 @@ +package iavlx + +import "bytes" + +// setRecursive do set operation. +// it always do modification and return new `MemNode`, even if the value is the same. +// also returns if it's an update or insertion, if update, the tree height and balance is not changed. +func setRecursive(nodePtr *NodePointer, leafNode *MemNode, ctx *MutationContext) (*NodePointer, bool, error) { + if nodePtr == nil { + return NewNodePointer(leafNode), true, nil + } + + node, err := nodePtr.Resolve() + if err != nil { + return nil, false, err + } + + nodeKey, err := node.Key() + if err != nil { + return nil, false, err + } + if node.IsLeaf() { + leafNodePtr := NewNodePointer(leafNode) + cmp := bytes.Compare(leafNode.key, nodeKey) + if cmp == 0 { + ctx.AddOrphan(nodePtr.id) + return leafNodePtr, true, nil + } + n := &MemNode{ + height: 1, + size: 2, + version: ctx.Version, + } + switch cmp { + case -1: + n.left = leafNodePtr + n.right = nodePtr + n.key = nodeKey + // n._keyRef = node + case 1: + n.left = nodePtr + n.right = leafNodePtr + n.key = leafNode.key + // n._keyRef = leafNode + default: + panic("unreachable") + } + return NewNodePointer(n), false, nil + } else { + var ( + newChildPtr *NodePointer + newNode *MemNode + updated bool + err error + ) + if bytes.Compare(leafNode.key, nodeKey) == -1 { + newChildPtr, updated, err = setRecursive(node.Left(), leafNode, ctx) + if err != nil { + return nil, false, err + } + newNode, err = ctx.MutateBranch(node) + if err != nil { + return nil, false, err + } + newNode.left = newChildPtr + } else { + newChildPtr, updated, err = setRecursive(node.Right(), leafNode, ctx) + if err != nil { + return nil, false, err + } + newNode, err = ctx.MutateBranch(node) + if err != nil { + return nil, false, err + } + newNode.right = newChildPtr + } + + if !updated { + err = newNode.updateHeightSize() + if err != nil { + return nil, false, err + } + + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, false, err + } + } + + return NewNodePointer(newNode), updated, nil + } +} + +type newKeyWrapper struct { + key []byte + // keyRef keyRefLink +} + +// removeRecursive returns: +// - (nil, origNode, nil) -> nothing changed in subtree +// - (value, nil, nil) -> leaf node is removed +// - (value, new node, newKey) -> subtree changed +func removeRecursive(nodePtr *NodePointer, key []byte, ctx *MutationContext) (value []byte, newNodePtr *NodePointer, newKey *newKeyWrapper, err error) { + if nodePtr == nil { + return nil, nil, nil, nil + } + + node, err := nodePtr.Resolve() + if err != nil { + return nil, nil, nil, err + } + + nodeKey, err := node.Key() + if err != nil { + return nil, nil, nil, err + } + + if node.IsLeaf() { + if bytes.Equal(nodeKey, key) { + ctx.AddOrphan(nodePtr.id) + value, err := node.Value() + return value, nil, nil, err + } + return nil, nodePtr, nil, nil + } + + if bytes.Compare(key, nodeKey) == -1 { + value, newLeft, newKey, err := removeRecursive(node.Left(), key, ctx) + if err != nil { + return nil, nil, nil, err + } + + if value == nil { + return nil, nodePtr, nil, nil + } + + if newLeft == nil { + ctx.AddOrphan(nodePtr.id) + return value, node.Right(), &newKeyWrapper{ + key: nodeKey, + // keyRef: nodePtr, + }, nil + } + + newNode, err := ctx.MutateBranch(node) + if err != nil { + return nil, nil, nil, err + } + newNode.left = newLeft + err = newNode.updateHeightSize() + if err != nil { + return nil, nil, nil, err + } + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, nil, nil, err + } + + return value, NewNodePointer(newNode), newKey, nil + } + + value, newRight, newKey, err := removeRecursive(node.Right(), key, ctx) + if err != nil { + return nil, nil, nil, err + } + + if value == nil { + return nil, nodePtr, nil, nil + } + + if newRight == nil { + ctx.AddOrphan(nodePtr.id) + return value, node.Left(), nil, nil + } + + newNode, err := ctx.MutateBranch(node) + if err != nil { + return nil, nil, nil, err + } + + newNode.right = newRight + if newKey != nil { + newNode.key = newKey.key + // newNode._keyRef = newKey.keyRef + } + + err = newNode.updateHeightSize() + if err != nil { + return nil, nil, nil, err + } + + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, nil, nil, err + } + + return value, NewNodePointer(newNode), nil, nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) updateHeightSize() error { + leftNode, err := node.left.Resolve() + if err != nil { + return err + } + + rightNode, err := node.right.Resolve() + if err != nil { + return err + } + + node.height = maxUint8(leftNode.Height(), rightNode.Height()) + 1 + node.size = leftNode.Size() + rightNode.Size() + return nil +} + +func maxUint8(a, b uint8) uint8 { + if a > b { + return a + } + return b +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) reBalance(ctx *MutationContext) (*MemNode, error) { + balance, err := calcBalance(node) + if err != nil { + return nil, err + } + switch { + case balance > 1: + left, err := node.left.Resolve() + if err != nil { + return nil, err + } + + leftBalance, err := calcBalance(left) + if err != nil { + return nil, err + } + + if leftBalance >= 0 { + // left left + return node.rotateRight(ctx) + } + + // left right + newLeft, err := ctx.MutateBranch(left) + if err != nil { + return nil, err + } + newLeft, err = newLeft.rotateLeft(ctx) + if err != nil { + return nil, err + } + node.left = NewNodePointer(newLeft) + return node.rotateRight(ctx) + case balance < -1: + right, err := node.right.Resolve() + if err != nil { + return nil, err + } + + rightBalance, err := calcBalance(right) + if err != nil { + return nil, err + } + + if rightBalance <= 0 { + // right right + return node.rotateLeft(ctx) + } + + // right left + newRight, err := ctx.MutateBranch(right) + if err != nil { + return nil, err + } + newRight, err = newRight.rotateRight(ctx) + node.right = NewNodePointer(newRight) + return node.rotateLeft(ctx) + default: + // nothing changed + return node, err + } +} + +func calcBalance(node Node) (int, error) { + leftNode, err := node.Left().Resolve() + if err != nil { + return 0, err + } + + rightNode, err := node.Right().Resolve() + if err != nil { + return 0, err + } + + return int(leftNode.Height()) - int(rightNode.Height()), nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) rotateRight(ctx *MutationContext) (*MemNode, error) { + left, err := node.left.Resolve() + if err != nil { + return nil, err + } + newSelf, err := ctx.MutateBranch(left) + if err != nil { + return nil, err + } + node.left = left.Right() + newSelf.right = NewNodePointer(node) + + err = node.updateHeightSize() + if err != nil { + return nil, err + } + err = newSelf.updateHeightSize() + if err != nil { + return nil, err + } + + return newSelf, nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) rotateLeft(ctx *MutationContext) (*MemNode, error) { + right, err := node.right.Resolve() + if err != nil { + return nil, err + } + + newSelf, err := ctx.MutateBranch(right) + if err != nil { + return nil, err + } + + node.right = right.Left() + newSelf.left = NewNodePointer(node) + + err = node.updateHeightSize() + if err != nil { + return nil, err + } + + err = newSelf.updateHeightSize() + if err != nil { + return nil, err + } + + return newSelf, nil +} diff --git a/iavlx/non_blocking_queue.go b/iavlx/non_blocking_queue.go new file mode 100644 index 000000000000..aaf89c20a82a --- /dev/null +++ b/iavlx/non_blocking_queue.go @@ -0,0 +1,57 @@ +package iavlx + +import "sync" + +type NonBlockingQueue[T any] struct { + mu sync.Mutex + cond *sync.Cond + queue []T + closed bool +} + +func NewNonBlockingQueue[T any]() *NonBlockingQueue[T] { + res := &NonBlockingQueue[T]{} + res.cond = sync.NewCond(&res.mu) + return res +} + +func (q *NonBlockingQueue[T]) Send(item T) { + q.mu.Lock() + defer q.mu.Unlock() + if q.closed { + panic("NonBlockingQueue is closed, can't send") + } + q.queue = append(q.queue, item) + q.cond.Signal() +} + +func (q *NonBlockingQueue[T]) Receive() []T { + q.mu.Lock() + defer q.mu.Unlock() + for len(q.queue) == 0 && !q.closed { + q.cond.Wait() + } + + res := q.queue + q.queue = nil + return res +} + +func (q *NonBlockingQueue[T]) MaybeReceive() (batch []T, closed bool) { + q.mu.Lock() + defer q.mu.Unlock() + if len(q.queue) == 0 && !q.closed { + return nil, false + } + + res := q.queue + q.queue = nil + return res, q.closed +} + +func (q *NonBlockingQueue[T]) Close() { + q.mu.Lock() + defer q.mu.Unlock() + q.closed = true + q.cond.Broadcast() +} diff --git a/iavlx/options.go b/iavlx/options.go new file mode 100644 index 000000000000..ff1dfb174863 --- /dev/null +++ b/iavlx/options.go @@ -0,0 +1,100 @@ +package iavlx + +import "time" + +type Options struct { + // EvictDepth defines the depth at which eviction occurs. 255 means no eviction. + EvictDepth uint8 `json:"evict_depth"` + + // WriteWAL enables write-ahead logging for durability + WriteWAL bool `json:"write_wal"` + // CompactWAL determines if KV data is copied during compaction (true) or reused (false) + CompactWAL bool `json:"compact_wal"` + // DisableCompaction turns off background compaction entirely + DisableCompaction bool `json:"disable_compaction"` + + // CompactionOrphanRatio is the orphan/total ratio (0-1) that triggers compaction + CompactionOrphanRatio float64 `json:"compaction_orphan_ratio"` + // CompactionOrphanAge is the average age of orphans (in versions) at which compaction is triggered + CompactionOrphanAge uint32 `json:"compaction_orphan_age"` + + // RetainVersions is the number of recent versions to keep uncompacted. + // If this is set to 0, all versions will be retained, and the compactor will only join changesets without removing any. + RetainVersions uint32 `json:"retain_versions"` + // MinCompactionSeconds is the minimum interval between compaction runs + MinCompactionSeconds uint32 `json:"min_compaction_seconds"` + // ChangesetMaxTarget is the maximum size of a changeset file when batching new versions + ChangesetMaxTarget uint32 `json:"changeset_max_target"` + // CompactionMaxTarget is the maximum size when joining/compacting old changesets + CompactionMaxTarget uint32 `json:"compaction_max_target"` + // CompactAfterVersions is the number of versions after which a full compaction is forced whenever there are orphans + CompactAfterVersions uint32 `json:"compact_after_versions"` + // ReaderUpdateInterval controls how often we create new mmap readers during batching (in versions) + // Setting to 0 means create reader every version (high mmap churn) + // Higher values reduce mmap overhead but delay when data becomes readable + ReaderUpdateInterval uint32 `json:"reader_update_interval"` + + // FsyncInterval defines how often to fsync WAL when using async mode (in millisconds). + FsyncInterval int `json:"fsync_interval"` + + // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement. + ZeroCopy bool `json:"zero_copy"` +} + +// GetCompactionOrphanAge returns the orphan age threshold with default +func (o Options) GetCompactionOrphanAge() uint32 { + if o.CompactionOrphanAge == 0 { + return 10 // Default to 10 versions + } + return o.CompactionOrphanAge +} + +// GetCompactionOrphanRatio returns the orphan ratio threshold with default +func (o Options) GetCompactionOrphanRatio() float64 { + if o.CompactionOrphanRatio <= 0 { + return 0.6 // Default to 60% orphans + } + return o.CompactionOrphanRatio +} + +// GetChangesetMaxTarget returns the max changeset size with default +func (o Options) GetChangesetMaxTarget() uint64 { + if o.ChangesetMaxTarget == 0 { + return 128 * 1024 * 1024 // 128MB default for changesets + } + return uint64(o.ChangesetMaxTarget) +} + +// GetCompactionMaxTarget returns the max size for compaction with default +func (o Options) GetCompactionMaxTarget() uint64 { + if o.CompactionMaxTarget == 0 { + return 1024 * 1024 * 1024 // 1GB default for compaction + } + return uint64(o.CompactionMaxTarget) +} + +func (o Options) GetCompactAfterVersions() uint32 { + if o.CompactAfterVersions == 0 { + return 500 // default to 500 versions + } + return o.CompactAfterVersions +} + +// GetReaderUpdateInterval returns the interval for creating readers with default +func (o Options) GetReaderUpdateInterval() uint32 { + if o.ReaderUpdateInterval == 0 { + return 100 // Default to updating reader every 100 versions + } + return o.ReaderUpdateInterval +} + +func (o Options) FsyncEnabled() bool { + return o.FsyncInterval != 0 +} + +func (o Options) GetFsyncInterval() time.Duration { + if o.FsyncInterval < 0 { + return 0 + } + return time.Millisecond * time.Duration(o.FsyncInterval) +} diff --git a/iavlx/orphans.go b/iavlx/orphans.go new file mode 100644 index 000000000000..e2cb2395753f --- /dev/null +++ b/iavlx/orphans.go @@ -0,0 +1,61 @@ +package iavlx + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "os" +) + +type OrphanWriter struct { + *FileWriter +} + +func NewOrphanWriter(file *os.File) *OrphanWriter { + return &OrphanWriter{ + FileWriter: NewFileWriter(file), + } +} + +func (ow *OrphanWriter) WriteOrphan(version uint32, id NodeID) error { + var bz [12]byte + binary.LittleEndian.PutUint32(bz[0:4], version) + binary.LittleEndian.PutUint64(bz[4:12], uint64(id)) + _, err := ow.Write(bz[:]) + return err +} + +func (ow *OrphanWriter) WriteOrphanMap(orphanMap map[NodeID]uint32) error { + for id, version := range orphanMap { + if err := ow.WriteOrphan(version, id); err != nil { + return err + } + } + + return ow.Flush() +} + +func ReadOrphanMap(file *os.File) (map[NodeID]uint32, error) { + file2, err := os.Open(file.Name()) + if err != nil { + return nil, fmt.Errorf("failed to open orphan file for reading: %w", err) + } + orphanMap := make(map[NodeID]uint32) + rdr := bufio.NewReader(file2) + var buf [12]byte + for { + _, err := rdr.Read(buf[:]) + if err != nil { + if err == io.EOF { + return orphanMap, nil + } + return nil, err + } + version := binary.LittleEndian.Uint32(buf[0:4]) + id := NodeID(binary.LittleEndian.Uint64(buf[4:12])) + if _, exists := orphanMap[id]; !exists { + orphanMap[id] = version + } + } +} diff --git a/iavlx/reader.go b/iavlx/reader.go new file mode 100644 index 000000000000..b9c9c82ac469 --- /dev/null +++ b/iavlx/reader.go @@ -0,0 +1,131 @@ +package iavlx + +import ( + "fmt" + "os" + "unsafe" +) + +// check little endian at init time +func init() { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + if buf != [2]byte{0xCD, 0xAB} { + panic("native byte order is not little endian, please build without nativebyteorder") + } +} + +type StructMmap[T any] struct { + items []T + file *MmapFile + size int +} + +func NewStructReader[T any](file *os.File) (*StructMmap[T], error) { + mmap, err := NewMmapFile(file) + if err != nil { + return nil, err + } + + var zero T + df := &StructMmap[T]{ + file: mmap, + size: int(unsafe.Sizeof(zero)), + } + + buf := mmap.Data() + p := unsafe.Pointer(unsafe.SliceData(buf)) + align := unsafe.Alignof(zero) + if uintptr(p)%align != 0 { + return nil, fmt.Errorf("input buffer is not aligned: %p", p) + } + + size := df.size + if len(buf)%size != 0 { + return nil, fmt.Errorf("input buffer size is not a multiple of struct size: %d %% %d != 0", len(buf), size) + } + data := unsafe.Slice((*T)(p), len(buf)/size) + df.items = data + + return df, nil +} + +func (df *StructMmap[T]) UnsafeItem(i uint32) *T { + return &df.items[i] +} + +func (df *StructMmap[T]) Count() int { + return len(df.items) +} + +func (df *StructMmap[T]) TotalBytes() int { + return df.file.TotalBytes() +} + +func (df *StructMmap[T]) Close() error { + return df.file.Close() +} + +type NodeLayout interface { + ID() NodeID +} + +type NodeMmap[T NodeLayout] struct { + *StructMmap[T] +} + +func NewNodeReader[T NodeLayout](file *os.File) (*NodeMmap[T], error) { + sf, err := NewStructReader[T](file) + if err != nil { + return nil, err + } + return &NodeMmap[T]{StructMmap: sf}, nil +} + +func (nf *NodeMmap[T]) FindByID(id NodeID, info *NodeSetInfo) (*T, error) { + // binary search with interpolation + lowOffset := info.StartOffset + targetIdx := id.Index() + lowIdx := info.StartIndex + highOffset := lowOffset + info.Count - 1 + highIdx := info.EndIndex + for lowOffset <= highOffset { + if targetIdx < lowIdx || targetIdx > highIdx { + return nil, fmt.Errorf("node ID %s not present", id.String()) + } + // If nodes are contiguous in this range, compute offset directly + if highIdx-lowIdx == highOffset-lowOffset { + targetOffset := lowOffset + (targetIdx - lowIdx) + return &nf.items[targetOffset], nil + } + // Interpolation search: estimate position based on target's relative position in index range + var mid uint32 + if highIdx > lowIdx { + // Estimate where target should be based on its position in the index range + fraction := float64(targetIdx-lowIdx) / float64(highIdx-lowIdx) + mid = lowOffset + uint32(fraction*float64(highOffset-lowOffset)) + // Ensure mid stays within bounds + if mid < lowOffset { + mid = lowOffset + } else if mid > highOffset { + mid = highOffset + } + } else { + // When indices converge, use simple midpoint + mid = (lowOffset + highOffset) / 2 + } + midNode := &nf.items[mid] + midIdx := (*midNode).ID().Index() + if midIdx == targetIdx { + return midNode, nil + } else if midIdx < targetIdx { + lowOffset = mid + 1 + lowIdx = midIdx + 1 + } else { + highOffset = mid - 1 + highIdx = midIdx - 1 + } + } + return nil, fmt.Errorf("node ID %s not found", id.String()) +} diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go new file mode 100644 index 000000000000..c4bf4cf0f946 --- /dev/null +++ b/iavlx/tree_store.go @@ -0,0 +1,384 @@ +package iavlx + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/tidwall/btree" + + "cosmossdk.io/log" +) + +type TreeStore struct { + logger log.Logger + dir string + + currentWriter *ChangesetWriter + currentChangesetEntry *changesetEntry // Entry for the current batch being written + changesets *btree.Map[uint32, *changesetEntry] + changesetsMapLock sync.RWMutex + savedVersion atomic.Uint32 // Last version with a readable changeset + stagedVersion uint32 // Latest written version (may not be readable yet) + + opts Options + + syncQueue *NonBlockingQueue[*ChangesetWriter] + syncDone chan error + + cleanupProc *cleanupProc +} + +type markOrphansReq struct { + version uint32 + orphans [][]NodeID +} + +type changesetEntry struct { + changeset atomic.Pointer[Changeset] +} + +func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, error) { + ts := &TreeStore{ + dir: dir, + changesets: &btree.Map[uint32, *changesetEntry]{}, + logger: logger, + opts: options, + stagedVersion: 1, + } + + err := ts.load() + if err != nil { + return nil, fmt.Errorf("failed to load existing changesets: %w", err) + } + + err = ts.initNewWriter() + if err != nil { + return nil, fmt.Errorf("failed to initialize first writer: %w", err) + } + + ts.cleanupProc = newCleanupProc(ts) + + if options.FsyncEnabled() { + ts.syncQueue = NewNonBlockingQueue[*ChangesetWriter]() + ts.syncDone = make(chan error) + go ts.syncProc() + } + + return ts, nil +} + +func (ts *TreeStore) initNewWriter() error { + stagedVersion := ts.savedVersion.Load() + 1 + writer, err := NewChangesetWriter(ts.dir, stagedVersion, ts) + if err != nil { + return fmt.Errorf("failed to create changeset writer: %w", err) + } + ts.currentWriter = writer + + return nil +} + +func (ts *TreeStore) getChangesetEntryForVersion(version uint32) *changesetEntry { + ts.changesetsMapLock.RLock() + defer ts.changesetsMapLock.RUnlock() + + var res *changesetEntry + // Find the changeset with the highest start version <= the requested version + ts.changesets.Descend(version, func(key uint32, cs *changesetEntry) bool { + res = cs + return false // Take the first (highest) entry <= version + }) + return res +} + +func (ts *TreeStore) getChangesetForVersion(version uint32) *Changeset { + cs := ts.getChangesetEntryForVersion(version) + if cs == nil { + return nil + } else { + return cs.changeset.Load() + } +} + +func (ts *TreeStore) ReadK(nodeId NodeID) (key []byte, err error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + cs.Pin() + defer cs.Unpin() + + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + var offset uint32 + if nodeId.IsLeaf() { + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + } + offset = leaf.KeyOffset + } else { + branch, err := cs.ResolveBranch(nodeId, 0) + if err != nil { + return nil, fmt.Errorf("failed to resolve branch %s: %w", nodeId.String(), err) + } + offset = branch.KeyOffset + } + + return cs.ReadK(nodeId, offset) +} + +func (ts *TreeStore) ReadKV(nodeId NodeID) (key, value []byte, err error) { + if !nodeId.IsLeaf() { + return nil, nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) + } + + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + cs.Pin() + defer cs.Unpin() + + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + } + + return cs.ReadKV(nodeId, leaf.KeyOffset) +} + +func (ts *TreeStore) ReadV(nodeId NodeID) ([]byte, error) { + // TODO reduce code duplication with ReadKV + + if !nodeId.IsLeaf() { + return nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) + } + + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + cs.Pin() + defer cs.Unpin() + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + } + + return cs.ReadV(nodeId, leaf.KeyOffset) +} + +func (ts *TreeStore) ResolveLeaf(nodeId NodeID) (LeafLayout, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return LeafLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + return cs.ResolveLeaf(nodeId, 0) +} + +func (ts *TreeStore) ResolveBranch(nodeId NodeID) (BranchLayout, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return BranchLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + return cs.ResolveBranch(nodeId, 0) +} + +func (ts *TreeStore) Resolve(nodeId NodeID, _ uint32) (Node, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + return cs.Resolve(nodeId, 0) +} + +func (ts *TreeStore) ResolveRoot(version uint32) (*NodePointer, error) { + cs := ts.getChangesetForVersion(version) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", version) + } + return cs.ResolveRoot(version) +} + +func (ts *TreeStore) SavedVersion() uint32 { + return ts.savedVersion.Load() +} + +func (ts *TreeStore) WriteWALUpdates(updates []KVUpdate) error { + return ts.currentWriter.WriteWALUpdates(updates) +} + +func (ts *TreeStore) WriteWALCommit(version uint32) error { + return ts.currentWriter.WriteWALCommit(version) +} + +func (ts *TreeStore) SaveRoot(root *NodePointer, totalLeaves, totalBranches uint32) error { + version := ts.stagedVersion + ts.logger.Debug("saving root", "version", version) + err := ts.currentWriter.SaveRoot(root, version, totalLeaves, totalBranches) + if err != nil { + return err + } + ts.stagedVersion++ + + currentSize := ts.currentWriter.TotalBytes() + maxSize := ts.opts.GetChangesetMaxTarget() + readerInterval := ts.opts.GetReaderUpdateInterval() + + ts.logger.Debug("saved root", "version", version, "changeset_size", currentSize, "max_size", maxSize, "start_version", ts.currentWriter.StartVersion()) + + // Queue changeset for async WAL sync if enabled + if ts.syncQueue != nil { + select { + case err := <-ts.syncDone: + if err != nil { + return err + } + default: + } + } + + // Determine if we should create a reader + shouldCreateReader := false + shouldSeal := uint64(currentSize) >= maxSize + + startVersion := ts.currentWriter.StartVersion() + if shouldSeal { + shouldCreateReader = true + } else if readerInterval > 0 { + // Create reader periodically based on interval + versions := version - startVersion + 1 + if versions%readerInterval == 0 { + shouldCreateReader = true + } + } + + if !shouldCreateReader { + // Just continue batching without creating reader + return nil + } + + // Create reader (either shared or sealed) + var reader *Changeset + if shouldSeal { + // Size limit reached - seal the current batch + curWriter := ts.currentWriter + reader, err = curWriter.Seal() + if err != nil { + return fmt.Errorf("failed to seal changeset for version %d: %w", version, err) + } + if ts.syncQueue != nil { + // if sync queue is enabled, queue the files for fsync + ts.syncQueue.Send(curWriter) + } + } else { + // Create shared reader for periodic update + reader, err = ts.currentWriter.CreatedSharedReader() + if err != nil { + return fmt.Errorf("failed to create updated changeset reader: %w", err) + } + } + + ts.setActiveReader(startVersion, reader) + ts.savedVersion.Store(version) + + if shouldSeal { + ts.currentChangesetEntry = nil // Reset for next batch + + // Create new writer for next batch + err = ts.initNewWriter() + if err != nil { + return fmt.Errorf("failed to initialize new writer after sealing version %d: %w", version, err) + } + } + + return nil +} + +func (ts *TreeStore) setActiveReader(version uint32, reader *Changeset) { + if ts.currentChangesetEntry == nil { + // First time we're creating an entry for this batch + ts.currentChangesetEntry = &changesetEntry{} + ts.currentChangesetEntry.changeset.Store(reader) + + // Register at the start version only + ts.changesetsMapLock.Lock() + ts.changesets.Set(version, ts.currentChangesetEntry) + ts.changesetsMapLock.Unlock() + } else { + // Update existing entry with new reader + oldReader := ts.currentChangesetEntry.changeset.Swap(reader) + if oldReader != nil { + oldReader.Evict() + if !oldReader.TryDispose() { + ts.cleanupProc.addPendingDisposal(oldReader) + } + } + } +} + +func (ts *TreeStore) MarkOrphans(version uint32, nodeIds [][]NodeID) { + ts.cleanupProc.markOrphans(version, nodeIds) +} + +func (ts *TreeStore) syncProc() { + tick := time.NewTicker(ts.opts.GetFsyncInterval()) + defer close(ts.syncDone) + for { + <-tick.C + curWriter := ts.currentWriter + err := curWriter.SyncWAL() + if err != nil { + ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) + return + } + needsSync, closed := ts.syncQueue.MaybeReceive() + for _, f := range needsSync { + if err := f.SyncWAL(); err != nil { + ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) + return + } + } + if closed { + return + } + } +} + +func (ts *TreeStore) Close() error { + // save the current writer if it has uncommitted data + startVersion := ts.currentWriter.files.info.StartVersion + if startVersion != 0 { + cs, err := ts.currentWriter.Seal() + if err != nil { + return fmt.Errorf("failed to seal current changeset on close: %w", err) + } + ts.setActiveReader(startVersion, cs) + ts.savedVersion.Store(ts.currentWriter.files.info.EndVersion) + } + + ts.cleanupProc.shutdown() + + if ts.syncQueue != nil { + ts.syncQueue.Close() + err := <-ts.syncDone + if err != nil { + return err + } + } + + ts.changesetsMapLock.Lock() + + var errs []error + ts.changesets.Scan(func(version uint32, entry *changesetEntry) bool { + errs = append(errs, entry.changeset.Load().Close()) + return true + }) + return errors.Join(errs...) +} diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go new file mode 100644 index 000000000000..4de85fb91d83 --- /dev/null +++ b/iavlx/tree_test.go @@ -0,0 +1,422 @@ +package iavlx + +import ( + "bytes" + "fmt" + "os" + "runtime/debug" + "testing" + + "github.com/cosmos/iavl" + dbm "github.com/cosmos/iavl/db" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "pgregory.net/rapid" + + corestore "cosmossdk.io/core/store" + sdklog "cosmossdk.io/log" + + storetypes "cosmossdk.io/store/types" +) + +// TODO: this test isn't for expected behavior. +// It should eventually be updated such that having a default ReaderUpdateInterval shouldn't error on old version queries. +func TestTree_ErrorsOnOldVersion(t *testing.T) { + testCases := []struct { + name string + getTree func() *CommitTree + expError error + }{ + { + name: "should error", + getTree: func() *CommitTree { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) + require.NoError(t, err) + return commitTree + }, + // TODO: this shouldn't error! + expError: fmt.Errorf("no changeset found for version 2"), + }, + { + name: "should NOT error", + getTree: func() *CommitTree { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger()) + require.NoError(t, err) + return commitTree + }, + expError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + commitTree := tc.getTree() + for range 7 { + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) + tree.Set([]byte{0}, []byte{1}) + tree.Write() + commitTree.Commit() + } + _, err := commitTree.GetImmutable(2) + require.Equal(t, tc.expError, err) + }) + } + +} + +func TestTree_NonExistentChangeset(t *testing.T) { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger()) + require.NoError(t, err) + + for range 7 { + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) + tree.Set([]byte{0}, []byte{1}) + tree.Write() + commitTree.Commit() + } + + _, err = commitTree.GetImmutable(2) + require.NoError(t, err) +} + +func TestBasicTest(t *testing.T) { + dir, err := os.MkdirTemp("", "iavlx") + require.NoError(t, err) + defer os.RemoveAll(dir) + commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) + require.NoError(t, err) + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) + tree.Set([]byte{0}, []byte{1}) + // renderTree(t, tree) + + val := tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + + tree.Set([]byte{1}, []byte{2}) + // renderTree(t, tree) + + val = tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + val = tree.Get([]byte{1}) + require.NoError(t, err) + require.Equal(t, []byte{2}, val) + + tree.Set([]byte{2}, []byte{3}) + // renderTree(t, tree) + + val = tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + val = tree.Get([]byte{1}) + require.NoError(t, err) + require.Equal(t, []byte{2}, val) + val = tree.Get([]byte{2}) + require.NoError(t, err) + require.Equal(t, []byte{3}, val) + + val = tree.Get([]byte{3}) + require.NoError(t, err) + require.Nil(t, val) + + tree.Delete([]byte{1}) + // renderTree(t, tree) + + val = tree.Get([]byte{1}) + require.NoError(t, err) + require.Nil(t, val) + + tree.Write() + commitId := commitTree.Commit() + require.NoError(t, err) + require.NotNil(t, commitId) + t.Logf("committed with root commitId: %X", commitId) + require.NoError(t, commitTree.Close()) +} + +func renderTree(t interface { + require.TestingT + Logf(format string, args ...any) +}, tree *ImmutableTree, +) { + graph := &bytes.Buffer{} + require.NoError(t, RenderNodeDotGraph(graph, tree.root)) + t.Logf("tree graph:\n%s", graph.String()) +} + +func TestIAVLXSims(t *testing.T) { + rapid.Check(t, testIAVLXSims) +} + +func FuzzIAVLX(f *testing.F) { + f.Fuzz(rapid.MakeFuzz(testIAVLXSims)) +} + +func testIAVLXSims(t *rapid.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("panic recovered: %v\nStack trace:\n%s", r, debug.Stack()) + } + }() + // logger := sdklog.NewTestLogger(t) + logger := sdklog.NewNopLogger() + dbV1 := dbm.NewMemDB() + treeV1 := iavl.NewMutableTree(dbV1, 500000, true, logger) + + tempDir, err := os.MkdirTemp("", "iavlx") + require.NoError(t, err, "failed to create temp directory") + defer os.RemoveAll(tempDir) + simMachine := &SimMachine{ + treeV1: treeV1, + dirV2: tempDir, + existingKeys: map[string][]byte{}, + } + simMachine.openV2Tree(t) + + // TODO switch from StateMachineActions to manually setting up the actions map, this is going to be too magical for other maintainers otherwise + t.Repeat(map[string]func(*rapid.T){ + "": simMachine.Check, + "UpdateN": simMachine.UpdateN, + "GetN": simMachine.GetN, + "Iterate": simMachine.Iterate, + "Commit": simMachine.Commit, + }) + + require.NoError(t, treeV1.Close(), "failed to close iavl tree") + require.NoError(t, simMachine.treeV2.Close(), "failed to close iavlx tree") +} + +type SimMachine struct { + treeV1 *iavl.MutableTree + treeV2 *CommitTree + dirV2 string + // existingKeys keeps track of keys that have been set in the tree or deleted. Deleted keys are retained as nil values. + existingKeys map[string][]byte +} + +func (s *SimMachine) openV2Tree(t interface { + require.TestingT + sdklog.TestingT +}) { + var err error + s.treeV2, err = NewCommitTree(s.dirV2, Options{ + WriteWAL: true, + CompactWAL: true, + DisableCompaction: true, + ZeroCopy: false, + EvictDepth: 0, + CompactionOrphanRatio: 0, + CompactionOrphanAge: 0, + RetainVersions: 0, + MinCompactionSeconds: 0, + ChangesetMaxTarget: 1, + CompactAfterVersions: 0, + ReaderUpdateInterval: 1, + }, sdklog.NewTestLogger(t)) + require.NoError(t, err, "failed to create iavlx tree") +} + +func (s *SimMachine) Check(t *rapid.T) { + // after every operation verify the iavlx tree + // after every operation we check that both trees are identical + s.compareIterators(t, nil, nil, true) +} + +func (s *SimMachine) UpdateN(t *rapid.T) { + n := rapid.IntRange(1, 5000).Draw(t, "n") + for i := 0; i < n; i++ { + del := rapid.Bool().Draw(t, "del") + if del { + s.delete(t) + } else { + s.set(t) + } + } +} + +func (s *SimMachine) GetN(t *rapid.T) { + n := rapid.IntRange(1, 5000).Draw(t, "n") + for i := 0; i < n; i++ { + s.get(t) + } +} + +func (s *SimMachine) set(t *rapid.T) { + // choose either a new or an existing key + key := s.selectKey(t) + value := rapid.SliceOfN(rapid.Byte(), 0, 10).Draw(t, "value") + // set in both trees + updated, errV1 := s.treeV1.Set(key, value) + require.NoError(t, errV1, "failed to set key in V1 tree") + branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore) + branch.Set(key, value) + branch.Write() + // require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") + if updated { + require.NotNil(t, s.existingKeys[string(key)], "key shouldn't have been marked as updated") + } else { + existing, found := s.existingKeys[string(key)] + if found { + require.Nil(t, existing, value, "marked as not an update but existin key is nil") + } + } + s.existingKeys[string(key)] = value // mark as existing +} + +func (s *SimMachine) get(t *rapid.T) { + key := s.selectKey(t) + valueV1, errV1 := s.treeV1.Get(key) + require.NoError(t, errV1, "failed to get key from V1 tree") + valueV2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Get(key) + require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + expectedValue, found := s.existingKeys[string(key)] + if found { + require.Equal(t, expectedValue, valueV1, "expected value mismatch for key %s", key) + } else { + require.Nil(t, valueV1, "expected nil value for non-existing key %s", key) + } +} + +func (s *SimMachine) selectKey(t *rapid.T) []byte { + if len(s.existingKeys) > 0 && rapid.Bool().Draw(t, "existingKey") { + return []byte(rapid.SampledFrom(maps.Keys(s.existingKeys)).Draw(t, "key")) + } else { + // TODO consider testing longer keys + return rapid.SliceOfN(rapid.Byte(), 1, 10).Draw(t, "key") + } +} + +func (s *SimMachine) delete(t *rapid.T) { + key := s.selectKey(t) + existingValue, found := s.existingKeys[string(key)] + exists := found && existingValue != nil + // delete in both trees + _, removedV1, errV1 := s.treeV1.Remove(key) + require.NoError(t, errV1, "failed to remove key from V1 tree") + branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore) + branch.Delete(key) + branch.Write() + // require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") + // TODO v1 & v2 have slightly different behaviors for the value returned on removal. We should re-enable this and check. + //if valueV1 == nil || len(valueV1) == 0 { + // require.Empty(t, valueV2, "value should be empty for removed key in V2 tree") + //} else { + // require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + //} + require.Equal(t, exists, removedV1, "removed status should match existence of key") + s.existingKeys[string(key)] = nil // mark as deleted +} + +func (s *SimMachine) Iterate(t *rapid.T) { + start := s.selectKey(t) + end := s.selectKey(t) + // make sure end is after start + if string(end) <= string(start) { + temp := start + start = end + end = temp + } + + // TODO add cases where we nudge start or end up or down a little + + // ascending := rapid.Bool().Draw(t, "ascending") + + // s.compareIterators(t, start, end, ascending) +} + +func (s *SimMachine) Commit(t *rapid.T) { + hash1, _, err := s.treeV1.SaveVersion() + require.NoError(t, err, "failed to save version in V1 tree") + commitId2 := s.treeV2.Commit() + require.NoError(t, err, "failed to save version in V2 tree") + err = VerifyTree(s.treeV2) + require.NoError(t, err, "failed to verify V2 tree") + require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees") + closeReopen := rapid.Bool().Draw(t, "closeReopen") + if closeReopen { + require.NoError(t, s.treeV2.Close()) + s.openV2Tree(t) + } +} + +func (s *SimMachine) debugDump(t *rapid.T) { + version := s.treeV1.Version() + t.Logf("Dumping trees at version %d", version) + graph1 := &bytes.Buffer{} + iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) + t.Logf("V1 tree:\n%s", graph1.String()) + // renderTree(t, s.treeV2.Branch()) + iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(nil, nil) + s.debugDumpTree(t, iter2) +} + +func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { + dumpStr := "Tree dump:" + defer func() { + require.NoError(t, iter.Close(), "failed to close iterator") + }() + for iter.Valid() { + key := iter.Key() + value := iter.Value() + dumpStr += fmt.Sprintf("\n\tKey: %X, Value: %X", key, value) + iter.Next() + } + t.Log(dumpStr) +} + +// func (s *SimMachine) CheckoutVersion(t *rapid.T) { +// if s.treeV1.Version() <= 1 { +// // cannot checkout version 1 or lower +// return +// } +// s.Commit(t) // make sure we've committed the current version before checking out a previous one +// curVersion := s.treeV1.Version() +// version := rapid.Int64Range(1, curVersion-1).Draw(t, "version") +// itreeV1, err := s.treeV1.GetImmutable(version) +// require.NoError(t, err, "failed to get immutable tree for V1 tree") +// err = s.treeV2.LoadVersion(version) +// require.NoError(t, err, "failed to load version in V2 tree") +// defer require.NoError(t, s.treeV2.LoadVersion(curVersion), "failed to reload current version in V2 tree") +// +// s.debugDumpTree(t) +// +// s.compareIterators(t, nil, nil, true) +// compareIteratorsAtVersion(t, itreeV1, s.treeV2, nil, nil, true) +//} + +func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) { + iter1, err1 := s.treeV1.Iterator(start, end, ascending) + require.NoError(t, err1, "failed to create iterator for V1 tree") + iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(start, end) + compareIteratorsAtVersion(t, iter1, iter2) +} + +func compareIteratorsAtVersion(t *rapid.T, iterV1, iterV2 corestore.Iterator) { + defer func() { + require.NoError(t, iterV1.Close(), "failed to close iterator for V1 tree") + }() + defer func() { + require.NoError(t, iterV2.Close(), "failed to close iterator for V2 tree") + }() + + for { + hasNextV1 := iterV1.Valid() + hasNextV2 := iterV2.Valid() + require.Equal(t, hasNextV1, hasNextV2, "iterator validity mismatch between V1 and V2 trees") + if !hasNextV1 { + break + } + keyV1 := iterV1.Key() + valueV1 := iterV1.Value() + keyV2 := iterV2.Key() + valueV2 := iterV2.Value() + require.Equal(t, keyV1, keyV2, "key mismatch between V1 and V2 trees") + require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + iterV1.Next() + iterV2.Next() + } +} diff --git a/iavlx/update.go b/iavlx/update.go new file mode 100644 index 000000000000..51342a50dbea --- /dev/null +++ b/iavlx/update.go @@ -0,0 +1,37 @@ +package iavlx + +type KVUpdate struct { + SetNode *MemNode + DeleteKey []byte +} + +type KVUpdateBatch struct { + Version uint32 + Orphans [][]NodeID + Updates []KVUpdate +} + +func NewKVUpdateBatch(stagedVersion uint32) *KVUpdateBatch { + return &KVUpdateBatch{ + Version: stagedVersion, + } +} + +type MutationContext struct { + Version uint32 + Orphans []NodeID +} + +func (ctx *MutationContext) MutateBranch(node Node) (*MemNode, error) { + id := node.ID() + if id != 0 { + ctx.Orphans = append(ctx.Orphans, id) + } + return node.MutateBranch(ctx.Version) +} + +func (ctx *MutationContext) AddOrphan(id NodeID) { + if id != 0 { + ctx.Orphans = append(ctx.Orphans, id) + } +} diff --git a/iavlx/verify.go b/iavlx/verify.go new file mode 100644 index 000000000000..25e012d75184 --- /dev/null +++ b/iavlx/verify.go @@ -0,0 +1,163 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +func VerifyTree(tree *CommitTree) error { + latest := tree.latest.Load() + if latest == nil { + return nil + } + + return verifyNode(latest) +} + +type DebugError struct { + Graph string + Err error +} + +func (d *DebugError) Error() string { + return fmt.Sprintf("%v\nDOT graph:\n%s", d.Err, d.Graph) +} + +func (d *DebugError) Unwrap() error { + return d.Err +} + +var _ error = &DebugError{} + +// func verifyNodeDebug(np *NodePointer) error { +// err := verifyNode(np) +// if err != nil { +// var dbgErr *DebugError +// if errors.As(err, &dbgErr) { +// return err +// } else { +// buf := &bytes.Buffer{} +// err2 := RenderNodeDotGraph(buf, np) +// if err2 == nil { +// err = &DebugError{ +// Graph: buf.String(), +// Err: err, +// } +// } +// } +// } +// return err +// } +func verifyNode(np *NodePointer) error { + node, err := np.Resolve() + if err != nil { + return fmt.Errorf("resolve node %s: %w", np.id, err) + } + + if node.Version() != uint32(np.id.Version()) { + return fmt.Errorf("node %s has version %d, expected %d", np.id, node.Version(), np.id.Version()) + } + + if node.IsLeaf() { + if node.Height() != 0 { + return fmt.Errorf("leaf node %s has height %d", np.id, node.Height()) + } + + if node.Size() != 1 { + return fmt.Errorf("leaf node %s has size %d, expected 1", np.id, node.Size()) + } + + if node.Left() != nil { + return fmt.Errorf("leaf node %s has non-nil left child", np.id) + } + + if node.Right() != nil { + return fmt.Errorf("leaf node %s has non-nil right child", np.id) + } + + hash, err := computeHash(node, nil, nil) + if err != nil { + return fmt.Errorf("compute hash for leaf node %s: %w", np.id, err) + } + + if !bytes.Equal(hash, node.Hash()) { + return fmt.Errorf("leaf node %s has invalid hash", np.id) + } + } else { + leftPtr := node.Left() + if leftPtr == nil { + return fmt.Errorf("branch node %s has nil left child", np.id) + } + + rightPtr := node.Right() + if rightPtr == nil { + return fmt.Errorf("branch node %s has nil right child", np.id) + } + + left, err := leftPtr.Resolve() + if err != nil { + return fmt.Errorf("resolve left child of node %s: %w", np.id, err) + } + + right, err := rightPtr.Resolve() + if err != nil { + return fmt.Errorf("resolve right child of node %s: %w", np.id, err) + } + + key, err := node.Key() + if err != nil { + return fmt.Errorf("get key of node %s: %w", np.id, err) + } + + leftKey, err := left.Key() + if err != nil { + return fmt.Errorf("get key of left child of node %s: %w", np.id, err) + } + + rightKey, err := right.Key() + if err != nil { + return fmt.Errorf("get key of right child of node %s: %w", np.id, err) + } + + if bytes.Compare(leftKey, key) >= 0 { + return fmt.Errorf("branch node %s with id %s has key %x, but left child %s, has key %x", node, np.id, key, left, leftKey) + } + + if bytes.Compare(rightKey, key) < 0 { + return fmt.Errorf("branch node %s with id %s has key %x, but right child %s, has key %x", node, np.id, key, right, rightKey) + } + + if left.Size()+right.Size() != node.Size() { + return fmt.Errorf("branch node %s has size %d, but children sizes are %d and %d", np.id, node.Size(), left.Size(), right.Size()) + } + + expectedHeight := maxUint8(left.Height(), right.Height()) + 1 + if node.Height() != expectedHeight { + return fmt.Errorf("branch node %s has height %d, expected %d, left height %d, right height %d", np.id, node.Height(), expectedHeight, left.Height(), right.Height()) + } + + // ensure balanced + balance := int(left.Height()) - int(right.Height()) + if balance < -1 || balance > 1 { + return fmt.Errorf("branch node %s is unbalanced: left height %d, right height %d", np.id, left.Height(), right.Height()) + } + + hash, err := computeHash(node, left.Hash(), right.Hash()) + if err != nil { + return fmt.Errorf("compute hash for branch node %s: %w", np.id, err) + } + + if !bytes.Equal(hash, node.Hash()) { + return fmt.Errorf("branch node %s has invalid hash", np.id) + } + + if err := verifyNode(leftPtr); err != nil { + return err + } + + if err := verifyNode(rightPtr); err != nil { + return err + } + } + return nil +} diff --git a/iavlx/version_info.go b/iavlx/version_info.go new file mode 100644 index 000000000000..c98ae877d7ca --- /dev/null +++ b/iavlx/version_info.go @@ -0,0 +1,27 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(VersionInfo{}) != VersionInfoSize { + panic(fmt.Sprintf("invalid VersionInfo size: got %d, want %d", unsafe.Sizeof(VersionInfo{}), VersionInfoSize)) + } +} + +const VersionInfoSize = 40 + +type VersionInfo struct { + Leaves NodeSetInfo + Branches NodeSetInfo + RootID NodeID +} + +type NodeSetInfo struct { + StartOffset uint32 + Count uint32 + StartIndex uint32 + EndIndex uint32 +} diff --git a/iavlx/writer.go b/iavlx/writer.go new file mode 100644 index 000000000000..4c29e82a2b5d --- /dev/null +++ b/iavlx/writer.go @@ -0,0 +1,62 @@ +package iavlx + +import ( + "bufio" + "fmt" + "io" + "os" + "unsafe" +) + +type FileWriter struct { + writer *bufio.Writer + written int +} + +func NewFileWriter(file *os.File) *FileWriter { + return &FileWriter{ + writer: bufio.NewWriterSize(file, 512*1024 /* 512kb */), // TODO: maybe we can have this as a config option? + } +} + +func (f *FileWriter) Write(p []byte) (n int, err error) { + n, err = f.writer.Write(p) + f.written += n + return n, err +} + +func (f *FileWriter) Flush() error { + if err := f.writer.Flush(); err != nil { + return fmt.Errorf("failed to flush writer: %w", err) + } + return nil +} + +func (f *FileWriter) Size() int { + return f.written +} + +var _ io.Writer = (*FileWriter)(nil) + +type StructWriter[T any] struct { + size int + *FileWriter +} + +func NewStructWriter[T any](file *os.File) *StructWriter[T] { + fw := NewFileWriter(file) + + return &StructWriter[T]{ + size: int(unsafe.Sizeof(*new(T))), + FileWriter: fw, + } +} + +func (sw *StructWriter[T]) Append(x *T) error { + _, err := sw.Write(unsafe.Slice((*byte)(unsafe.Pointer(x)), sw.size)) + return err +} + +func (sw *StructWriter[T]) Count() int { + return sw.written / sw.size +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000000..0702e8609b8f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "cosmos-sdk" +version = "0.1.0" +description = "Add your description here" +requires-python = ">=3.13" +dependencies = [ + "duckdb>=1.4.1", + "notebook>=7.4.7", + "pandas>=2.3.3", + "plotly>=6.4.0", + "pydantic>=2.12.3", + "pytz>=2025.2", +] diff --git a/server/api/server.go b/server/api/server.go index 84de24c40784..5a107edd4a9f 100644 --- a/server/api/server.go +++ b/server/api/server.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "net" "net/http" "strings" @@ -24,7 +23,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/server/config" cmtlogwrapper "github.com/cosmos/cosmos-sdk/server/log" - "github.com/cosmos/cosmos-sdk/telemetry" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" ) @@ -35,7 +33,6 @@ type Server struct { ClientCtx client.Context GRPCSrv *grpc.Server logger log.Logger - metrics *telemetry.Metrics // Start() is blocking and generally called from a separate goroutine. // Close() can be called asynchronously and access shared memory @@ -191,31 +188,6 @@ func (s *Server) Close() error { return s.listener.Close() } -func (s *Server) SetTelemetry(m *telemetry.Metrics) { - s.mtx.Lock() - s.registerMetrics(m) - s.mtx.Unlock() -} - -func (s *Server) registerMetrics(m *telemetry.Metrics) { - s.metrics = m - - metricsHandler := func(w http.ResponseWriter, r *http.Request) { - format := strings.TrimSpace(r.FormValue("format")) - - gr, err := s.metrics.Gather(format) - if err != nil { - writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) - return - } - - w.Header().Set("Content-Type", gr.ContentType) - _, _ = w.Write(gr.Metrics) - } - - s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET") -} - // errorResponse defines the attributes of a JSON error response. type errorResponse struct { Code int `json:"code,omitempty"` diff --git a/server/config/config.go b/server/config/config.go index a50893e850c1..c9fa4351a087 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -8,7 +8,7 @@ import ( pruningtypes "cosmossdk.io/store/pruning/types" - "github.com/cosmos/cosmos-sdk/telemetry" + _ "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -190,13 +190,12 @@ type Config struct { BaseConfig `mapstructure:",squash"` // Telemetry defines the application telemetry configuration - Telemetry telemetry.Config `mapstructure:"telemetry"` - API APIConfig `mapstructure:"api"` - GRPC GRPCConfig `mapstructure:"grpc"` - GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` - StateSync StateSyncConfig `mapstructure:"state-sync"` - Streaming StreamingConfig `mapstructure:"streaming"` - Mempool MempoolConfig `mapstructure:"mempool"` + API APIConfig `mapstructure:"api"` + GRPC GRPCConfig `mapstructure:"grpc"` + GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` + StateSync StateSyncConfig `mapstructure:"state-sync"` + Streaming StreamingConfig `mapstructure:"streaming"` + Mempool MempoolConfig `mapstructure:"mempool"` } // SetMinGasPrices sets the validator's minimum gas prices. @@ -234,10 +233,6 @@ func DefaultConfig() *Config { IAVLDisableFastNode: false, AppDBBackend: "", }, - Telemetry: telemetry.Config{ - Enabled: false, - GlobalLabels: [][]string{}, - }, API: APIConfig{ Enable: false, Swagger: false, diff --git a/server/start.go b/server/start.go index aab620b27548..0c321241c77b 100644 --- a/server/start.go +++ b/server/start.go @@ -76,6 +76,7 @@ const ( FlagIAVLCacheSize = "iavl-cache-size" FlagDisableIAVLFastNode = "iavl-disable-fastnode" FlagIAVLSyncPruning = "iavl-sync-pruning" + FlagIAVLXOptions = "iavlx-options" FlagShutdownGrace = "shutdown-grace" // state sync-related flags @@ -230,20 +231,15 @@ func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreato } defer appCleanupFn() - metrics, err := startTelemetry(svrCfg) - if err != nil { - return fmt.Errorf("failed to start telemetry: %w", err) - } - emitServerInfoMetrics() if !withCmt { - return startStandAlone(svrCtx, svrCfg, clientCtx, app, metrics, opts) + return startStandAlone(svrCtx, svrCfg, clientCtx, app, opts) } - return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts) + return startInProcess(svrCtx, svrCfg, clientCtx, app, opts) } -func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions) error { +func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error { addr := svrCtx.Viper.GetString(flagAddress) transport := svrCtx.Viper.GetString(flagTransport) @@ -282,7 +278,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie return err } - err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv, metrics) + err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv) if err != nil { return err } @@ -309,9 +305,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie return g.Wait() } -func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, - metrics *telemetry.Metrics, opts StartCmdOptions, -) error { +func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error { cmtCfg := svrCtx.Config gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly) @@ -348,7 +342,7 @@ func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clien return fmt.Errorf("failed to start grpc server: %w", err) } - err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv, metrics) + err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv) if err != nil { return fmt.Errorf("failed to start api server: %w", err) } @@ -517,7 +511,6 @@ func startAPIServer( app types.Application, home string, grpcSrv *grpc.Server, - metrics *telemetry.Metrics, ) error { if !svrCfg.API.Enable { return nil @@ -528,20 +521,12 @@ func startAPIServer( apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv) app.RegisterAPIRoutes(apiSrv, svrCfg.API) - if svrCfg.Telemetry.Enabled { - apiSrv.SetTelemetry(metrics) - } - g.Go(func() error { return apiSrv.Start(ctx, svrCfg) }) return nil } -func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) { - return telemetry.New(cfg.Telemetry) -} - // wrapCPUProfile starts CPU profiling, if enabled, and executes the provided // callbackFn in a separate goroutine, then will wait for that callback to // return. @@ -623,6 +608,13 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions cleanupFn = func() { traceCleanupFn() + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := telemetry.Shutdown(shutdownCtx); err != nil { + svrCtx.Logger.Error("failed to shutdown telemetry", "error", err) + } + if localErr := app.Close(); localErr != nil { svrCtx.Logger.Error(localErr.Error()) } diff --git a/server/util.go b/server/util.go index 42d01bd65d09..6c99aacf80b4 100644 --- a/server/util.go +++ b/server/util.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -25,6 +26,7 @@ import ( "golang.org/x/sync/errgroup" "cosmossdk.io/log" + "cosmossdk.io/store" "cosmossdk.io/store/snapshots" snapshottypes "cosmossdk.io/store/snapshots/types" @@ -32,6 +34,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/iavlx" "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -582,6 +585,27 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { defaultMempool, baseapp.SetChainID(chainID), baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))), + func(bapp *baseapp.BaseApp) { + fmt.Println("Loading IAVLX as the commit multi-store...") + opts := &iavlx.Options{} + optsJson, ok := appOpts.Get(FlagIAVLXOptions).(string) + if ok && optsJson != "" { + err := json.Unmarshal([]byte(optsJson), opts) + if err != nil { + panic(fmt.Errorf("failed to unmarshal iavlx options: %w", err)) + } + } + + db, err := iavlx.LoadDB( + filepath.Join(homeDir, "data", "iavlx"), + opts, + bapp.Logger(), + ) + if err != nil { + panic(fmt.Errorf("failed to load iavlx db: %w", err)) + } + bapp.SetCMS(db) + }, } } diff --git a/simapp/app_di.go b/simapp/app_di.go index 972edc75fa1a..3975847f8172 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -3,19 +3,25 @@ package simapp import ( + "encoding/json" + "fmt" "io" + "os" + "path/filepath" dbm "github.com/cosmos/cosmos-db" clienthelpers "cosmossdk.io/client/v2/helpers" "cosmossdk.io/depinject" - "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/iavlx" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/api" @@ -100,6 +106,23 @@ func NewSimApp( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { + iavlxOpts, useIavlx := os.LookupEnv("IAVLX") + if useIavlx && iavlxOpts != "" { + fmt.Println("Setting up IAVLX as the underlying commit multi-store") + var opts iavlx.Options + err := json.Unmarshal([]byte(iavlxOpts), &opts) + if err != nil { + panic(err) + } + baseAppOptions = append(baseAppOptions, func(bApp *baseapp.BaseApp) { + dir := filepath.Join(appOpts.Get(flags.FlagHome).(string), "data", "iavlx") + db, err := iavlx.LoadDB(dir, &opts, logger) + if err != nil { + panic(err) + } + bApp.SetCMS(db) + }) + } var ( app = &SimApp{} appBuilder *runtime.AppBuilder diff --git a/simapp/go.mod b/simapp/go.mod index ee826a142b4b..f800ac5b76fa 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -41,7 +41,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -72,6 +71,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -101,6 +101,8 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -114,6 +116,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -131,8 +134,10 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -157,6 +162,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -172,16 +178,19 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect @@ -190,21 +199,42 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index c2ccd1cd3d36..1f5d87ad05a0 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -55,7 +55,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -163,6 +162,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -285,6 +286,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -349,6 +353,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -449,6 +456,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -457,6 +466,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -569,6 +580,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -688,6 +701,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -714,6 +729,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -721,8 +738,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -747,6 +764,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -809,6 +828,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -822,6 +845,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -837,25 +862,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -983,6 +1044,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1000,6 +1062,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1019,6 +1082,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/simapp/sim_test.go b/simapp/sim_test.go index c76a12735d23..4f2bf6ad95f0 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -19,11 +19,13 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/log" + "cosmossdk.io/store" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp" servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/telemetry" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sims "github.com/cosmos/cosmos-sdk/testutil/simsx" sdk "github.com/cosmos/cosmos-sdk/types" @@ -44,6 +46,10 @@ func init() { flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service") } +func TestMain(m *testing.M) { + telemetry.TestingMain(m, nil) +} + // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. func interBlockCacheOpt() func(*baseapp.BaseApp) { @@ -157,9 +163,10 @@ func IsEmptyValidatorSetErr(err error) bool { } func TestAppStateDeterminism(t *testing.T) { - const numTimesToRunPerSeed = 3 + cfg := simcli.NewConfigFromFlags() + numTimesToRunPerSeed := cfg.NumRuns var seeds []int64 - if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { + if s := cfg.Seed; s != simcli.DefaultSeedValue { // We will be overriding the random seed and just run a single simulation on the provided seed value for j := 0; j < numTimesToRunPerSeed; j++ { // multiple rounds seeds = append(seeds, s) diff --git a/systemtests/go.mod b/systemtests/go.mod index f24a9631d68c..3de7a43cf36a 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -26,7 +26,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -34,6 +33,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -60,6 +60,8 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -70,6 +72,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -80,11 +83,14 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -105,6 +111,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.3 // indirect @@ -116,16 +123,19 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -139,15 +149,38 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index a39502487f05..8f6e036e1264 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -31,7 +31,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -91,6 +90,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -199,6 +200,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -253,6 +257,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -346,6 +353,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -354,6 +363,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -456,6 +467,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -572,6 +585,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -598,6 +613,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -605,8 +622,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -631,6 +648,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -697,6 +716,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -708,6 +731,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -723,17 +748,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -846,6 +909,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -862,6 +926,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -880,6 +945,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/telemetry/README.md b/telemetry/README.md new file mode 100644 index 000000000000..d1f873423b51 --- /dev/null +++ b/telemetry/README.md @@ -0,0 +1,65 @@ +## Quick Start For Local Telemetry + +To quickly setup a local telemetry environment where OpenTelemetry data is sent to a local instance of Grafana LGTM: +1. start the [Grafana LGTM docker image](https://hub.docker.com/r/grafana/otel-lgtm): +```shell +docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm +``` +2. create a basic OpenTelemetry configuration file which will send data to the local instance of Grafana LGTM: +```yaml +resource: + attributes: + - name: service.name + value: my_app_name +tracer_provider: + processors: + - batch: # NOTE: you should use batch in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +meter_provider: + readers: + - periodic: + interval: 1000 # 1 second, maybe use something longer in production + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +logger_provider: + processors: + - batch: + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +``` +3. set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of the configuration file: +`export OTEL_EXPERIMENTAL_CONFIG_FILE=path/to/config.yaml` +4. start your application or tests +5. view the data in Grafana LGTM at http://localhost:3000/. The Drilldown views are suggested for getting started. + +## OpenTelemetry Initialization + +While manual OpenTelemetry initialization is still supported, this package provides a single +point of initialization such that end users can just use the official +OpenTelemetry declarative configuration spec: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ +End users only need to set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of +an OpenTelemetry configuration file and that's it. +All the documentation necessary is provided in the OpenTelemetry documentation. + +## Developer Usage + +Developers need to do two things to use this package properly: + 1. Import this package before declaring any otel Tracer, Meter or Logger instances. + 2. Make sure Shutdown() is called when the application is shutting down. + Tests can use the TestingInit function at startup to accomplish this. + +If these steps are followed, developers can follow the official golang otel conventions +of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter(). +NOTE: it is important to thread context.Context properly for spans, metrics, and logs to be +correlated correctly. +When using the SDK's context type, spans must be started with Context.StartSpan to +get an SDK context which has the span set correctly. +For logging, go.opentelemetry.io/contrib/bridges/otelslog provides a way to do this with the standard +library slog package. diff --git a/telemetry/config.go b/telemetry/config.go new file mode 100644 index 000000000000..6dedddfeacd2 --- /dev/null +++ b/telemetry/config.go @@ -0,0 +1,216 @@ +package telemetry + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "time" + + "go.opentelemetry.io/contrib/bridges/otelslog" + "go.opentelemetry.io/contrib/instrumentation/host" + "go.opentelemetry.io/contrib/instrumentation/runtime" + otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + logglobal "go.opentelemetry.io/otel/log/global" + logsdk "go.opentelemetry.io/otel/sdk/log" + metricsdk "go.opentelemetry.io/otel/sdk/metric" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.yaml.in/yaml/v3" +) + +var sdk otelconf.SDK +var shutdownFuncs []func(context.Context) error + +var isTelemetryEnabled = true + +func init() { + err := initOpenTelemetry() + if err != nil { + panic(err) + } +} + +func initOpenTelemetry() error { + var err error + + var opts []otelconf.ConfigurationOption + + confFilename := os.Getenv("OTEL_EXPERIMENTAL_CONFIG_FILE") + if confFilename != "" { + bz, err := os.ReadFile(confFilename) + if err != nil { + return fmt.Errorf("failed to read telemetry config file: %w", err) + } + + cfg, err := otelconf.ParseYAML(bz) + if err != nil { + return fmt.Errorf("failed to parse telemetry config file: %w", err) + } + + cfgJson, err := json.Marshal(cfg) + if err != nil { + return fmt.Errorf("failed to marshal telemetry config file: %w", err) + } + fmt.Printf("\nInitializing telemetry with config:\n%s\n\n", cfgJson) + + opts = append(opts, otelconf.WithOpenTelemetryConfiguration(*cfg)) + + // parse cosmos extra config + var extraCfg extraConfig + err = yaml.Unmarshal(bz, &extraCfg) + if err == nil { + if extraCfg.CosmosExtra != nil { + extra := *extraCfg.CosmosExtra + if extra.TraceFile != "" { + fmt.Printf("Initializing trace file: %s\n", extra.TraceFile) + traceFile, err := os.OpenFile(extra.TraceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open trace file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := traceFile.Close(); err != nil { + return fmt.Errorf("failed to close trace file: %w", err) + } + return nil + }) + exporter, err := stdouttrace.New( + stdouttrace.WithWriter(traceFile), + //stdouttrace.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout trace exporter: %w", err) + } + opts = append(opts, otelconf.WithTracerProviderOptions( + tracesdk.WithBatcher(exporter), + )) + } + if extra.MetricsFile != "" { + fmt.Printf("Initializing metrics file: %s\n", extra.MetricsFile) + metricsFile, err := os.OpenFile(extra.MetricsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open metrics file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := metricsFile.Close(); err != nil { + return fmt.Errorf("failed to close metrics file: %w", err) + } + return nil + }) + exporter, err := stdoutmetric.New( + stdoutmetric.WithWriter(metricsFile), + //stdoutmetric.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout metric exporter: %w", err) + } + + // Configure periodic reader with custom interval if specified + readerOpts := []metricsdk.PeriodicReaderOption{} + if extra.MetricsFileInterval != "" { + interval, err := time.ParseDuration(extra.MetricsFileInterval) + if err != nil { + return fmt.Errorf("failed to parse metrics_file_interval: %w", err) + } + fmt.Printf("Configuring metrics export interval: %v\n", interval) + readerOpts = append(readerOpts, metricsdk.WithInterval(interval)) + } + + opts = append(opts, otelconf.WithMeterProviderOptions( + metricsdk.WithReader(metricsdk.NewPeriodicReader(exporter, readerOpts...)), + )) + } + if extra.LogsFile != "" { + fmt.Printf("Initializing logs file: %s\n", extra.LogsFile) + logsFile, err := os.OpenFile(extra.LogsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open logs file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := logsFile.Close(); err != nil { + return fmt.Errorf("failed to close logs file: %w", err) + } + return nil + }) + exporter, err := stdoutlog.New( + stdoutlog.WithWriter(logsFile), + //stdoutlog.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout log exporter: %w", err) + } + opts = append(opts, otelconf.WithLoggerProviderOptions( + logsdk.WithProcessor(logsdk.NewBatchProcessor(exporter)), + )) + } + if extra.InstrumentHost { + fmt.Println("Initializing host instrumentation") + if err := host.Start(); err != nil { + return fmt.Errorf("failed to start host instrumentation: %w", err) + } + } + if extra.InstrumentRuntime { + fmt.Println("Initializing runtime instrumentation") + if err := runtime.Start(); err != nil { + return fmt.Errorf("failed to start runtime instrumentation: %w", err) + } + } + } + } else { + fmt.Printf("failed to parse cosmos extra config: %v\n", err) + } + } + + sdk, err = otelconf.NewSDK(opts...) + if err != nil { + return fmt.Errorf("failed to initialize telemetry: %w", err) + } + + // setup otel global providers + otel.SetTracerProvider(sdk.TracerProvider()) + otel.SetMeterProvider(sdk.MeterProvider()) + logglobal.SetLoggerProvider(sdk.LoggerProvider()) + // setup slog default provider so that any logs emitted the default slog will be traced + slog.SetDefault(otelslog.NewLogger("", otelslog.WithSource(true))) + // emit an initialized message which verifies basic telemetry is working + slog.Info("Telemetry initialized") + return nil +} + +type extraConfig struct { + CosmosExtra *cosmosExtra `json:"cosmos_extra" yaml:"cosmos_extra" mapstructure:"cosmos_extra"` +} + +type cosmosExtra struct { + TraceFile string `json:"trace_file" yaml:"trace_file" mapstructure:"trace_file"` + MetricsFile string `json:"metrics_file" yaml:"metrics_file" mapstructure:"metrics_file"` + MetricsFileInterval string `json:"metrics_file_interval" yaml:"metrics_file_interval" mapstructure:"metrics_file_interval"` + LogsFile string `json:"logs_file" yaml:"logs_file" mapstructure:"logs_file"` + InstrumentHost bool `json:"instrument_host" yaml:"instrument_host" mapstructure:"instrument_host"` + InstrumentRuntime bool `json:"instrument_runtime" yaml:"instrument_runtime" mapstructure:"instrument_runtime"` +} + +func Shutdown(ctx context.Context) error { + err := sdk.Shutdown(ctx) + if err != nil { + return fmt.Errorf("failed to shutdown telemetry: %w", err) + } + for _, f := range shutdownFuncs { + if err := f(ctx); err != nil { + return fmt.Errorf("failed to shutdown telemetry: %w", err) + } + } + return nil +} + +func IsTelemetryEnabled() bool { + return isTelemetryEnabled +} + +func SetTelemetryEnabled(v bool) { + isTelemetryEnabled = v +} diff --git a/telemetry/doc.go b/telemetry/doc.go new file mode 100644 index 000000000000..606ace81fdd8 --- /dev/null +++ b/telemetry/doc.go @@ -0,0 +1,2 @@ +// Package telemetry initializes OpenTelemetry and provides legacy metrics wrapper functions. +package telemetry diff --git a/telemetry/metrics.go b/telemetry/metrics.go deleted file mode 100644 index 67ace50c53ec..000000000000 --- a/telemetry/metrics.go +++ /dev/null @@ -1,241 +0,0 @@ -package telemetry - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "net/http" - "time" - - "github.com/hashicorp/go-metrics" - "github.com/hashicorp/go-metrics/datadog" - metricsprom "github.com/hashicorp/go-metrics/prometheus" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/expfmt" -) - -// globalTelemetryEnabled is a private variable that stores the telemetry enabled state. -// It is set on initialization and does not change for the lifetime of the program. -var globalTelemetryEnabled bool - -// IsTelemetryEnabled provides controlled access to check if telemetry is enabled. -func IsTelemetryEnabled() bool { - return globalTelemetryEnabled -} - -// EnableTelemetry allows for the global telemetry enabled state to be set. -func EnableTelemetry() { - globalTelemetryEnabled = true -} - -// globalLabels defines the set of global labels that will be applied to all -// metrics emitted using the telemetry package function wrappers. -var globalLabels = []metrics.Label{} - -// Metrics supported format types. -const ( - FormatDefault = "" - FormatPrometheus = "prometheus" - FormatText = "text" - ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8` - - MetricSinkInMem = "mem" - MetricSinkStatsd = "statsd" - MetricSinkDogsStatsd = "dogstatsd" -) - -// DisplayableSink is an interface that defines a method for displaying metrics. -type DisplayableSink interface { - DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error) -} - -// Config defines the configuration options for application telemetry. -type Config struct { - // Prefixed with keys to separate services - ServiceName string `mapstructure:"service-name"` - - // Enabled enables the application telemetry functionality. When enabled, - // an in-memory sink is also enabled by default. Operators may also enabled - // other sinks such as Prometheus. - Enabled bool `mapstructure:"enabled"` - - // Enable prefixing gauge values with hostname - EnableHostname bool `mapstructure:"enable-hostname"` - - // Enable adding hostname to labels - EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` - - // Enable adding service to labels - EnableServiceLabel bool `mapstructure:"enable-service-label"` - - // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. - // It defines the retention duration in seconds. - PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` - - // GlobalLabels defines a global set of name/value label tuples applied to all - // metrics emitted using the wrapper functions defined in telemetry package. - // - // Example: - // [["chain_id", "cosmoshub-1"]] - GlobalLabels [][]string `mapstructure:"global-labels"` - - // MetricsSink defines the type of metrics backend to use. - MetricsSink string `mapstructure:"metrics-sink" default:"mem"` - - // StatsdAddr defines the address of a statsd server to send metrics to. - // Only utilized if MetricsSink is set to "statsd" or "dogstatsd". - StatsdAddr string `mapstructure:"statsd-addr"` - - // DatadogHostname defines the hostname to use when emitting metrics to - // Datadog. Only utilized if MetricsSink is set to "dogstatsd". - DatadogHostname string `mapstructure:"datadog-hostname"` -} - -// Metrics defines a wrapper around application telemetry functionality. It allows -// metrics to be gathered at any point in time. When creating a Metrics object, -// internally, a global metrics is registered with a set of sinks as configured -// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a -// dump of formatted recent metrics will be sent to STDERR. -type Metrics struct { - sink metrics.MetricSink - prometheusEnabled bool -} - -// GatherResponse is the response type of registered metrics -type GatherResponse struct { - Metrics []byte - ContentType string -} - -// New creates a new instance of Metrics -func New(cfg Config) (_ *Metrics, rerr error) { - globalTelemetryEnabled = cfg.Enabled - if !cfg.Enabled { - return nil, nil - } - - if numGlobalLabels := len(cfg.GlobalLabels); numGlobalLabels > 0 { - parsedGlobalLabels := make([]metrics.Label, numGlobalLabels) - for i, gl := range cfg.GlobalLabels { - parsedGlobalLabels[i] = NewLabel(gl[0], gl[1]) - } - globalLabels = parsedGlobalLabels - } - - metricsConf := metrics.DefaultConfig(cfg.ServiceName) - metricsConf.EnableHostname = cfg.EnableHostname - metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel - - var ( - sink metrics.MetricSink - err error - ) - switch cfg.MetricsSink { - case MetricSinkStatsd: - sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) - case MetricSinkDogsStatsd: - sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) - default: - memSink := metrics.NewInmemSink(10*time.Second, time.Minute) - sink = memSink - inMemSig := metrics.DefaultInmemSignal(memSink) - defer func() { - if rerr != nil { - inMemSig.Stop() - } - }() - } - - if err != nil { - return nil, err - } - - m := &Metrics{sink: sink} - fanout := metrics.FanoutSink{sink} - - if cfg.PrometheusRetentionTime > 0 { - m.prometheusEnabled = true - prometheusOpts := metricsprom.PrometheusOpts{ - Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second, - } - - promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts) - if err != nil { - return nil, err - } - - fanout = append(fanout, promSink) - } - - if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil { - return nil, err - } - - return m, nil -} - -// Gather collects all registered metrics and returns a GatherResponse where the -// metrics are encoded depending on the type. Metrics are either encoded via -// Prometheus or JSON if in-memory. -func (m *Metrics) Gather(format string) (GatherResponse, error) { - switch format { - case FormatPrometheus: - return m.gatherPrometheus() - - case FormatText: - return m.gatherGeneric() - - case FormatDefault: - return m.gatherGeneric() - - default: - return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) - } -} - -// gatherPrometheus collects Prometheus metrics and returns a GatherResponse. -// If Prometheus metrics are not enabled, it returns an error. -func (m *Metrics) gatherPrometheus() (GatherResponse, error) { - if !m.prometheusEnabled { - return GatherResponse{}, errors.New("prometheus metrics are not enabled") - } - - metricsFamilies, err := prometheus.DefaultGatherer.Gather() - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err) - } - - buf := &bytes.Buffer{} - defer buf.Reset() - - e := expfmt.NewEncoder(buf, expfmt.NewFormat(expfmt.TypeTextPlain)) - - for _, mf := range metricsFamilies { - if err := e.Encode(mf); err != nil { - return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err) - } - } - - return GatherResponse{ContentType: ContentTypeText, Metrics: buf.Bytes()}, nil -} - -// gatherGeneric collects generic metrics and returns a GatherResponse. -func (m *Metrics) gatherGeneric() (GatherResponse, error) { - gm, ok := m.sink.(DisplayableSink) - if !ok { - return GatherResponse{}, errors.New("non in-memory metrics sink does not support generic format") - } - - summary, err := gm.DisplayMetrics(nil, nil) - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err) - } - - content, err := json.Marshal(summary) - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err) - } - - return GatherResponse{ContentType: "application/json", Metrics: content}, nil -} diff --git a/telemetry/metrics_test.go b/telemetry/metrics_test.go deleted file mode 100644 index b741f007db22..000000000000 --- a/telemetry/metrics_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package telemetry - -import ( - "encoding/json" - "strings" - "testing" - "time" - - "github.com/hashicorp/go-metrics" - "github.com/stretchr/testify/require" -) - -func TestMetrics_Disabled(t *testing.T) { - m, err := New(Config{Enabled: false}) - require.Nil(t, m) - require.Nil(t, err) -} - -func TestMetrics_InMem(t *testing.T) { - m, err := New(Config{ - MetricsSink: MetricSinkInMem, - Enabled: true, - EnableHostname: false, - ServiceName: "test", - }) - require.NoError(t, err) - require.NotNil(t, m) - - emitMetrics() - - gr, err := m.Gather(FormatText) - require.NoError(t, err) - require.Equal(t, gr.ContentType, "application/json") - - jsonMetrics := make(map[string]any) - require.NoError(t, json.Unmarshal(gr.Metrics, &jsonMetrics)) - - counters := jsonMetrics["Counters"].([]any) - require.Equal(t, counters[0].(map[string]any)["Count"].(float64), 10.0) - require.Equal(t, counters[0].(map[string]any)["Name"].(string), "test.dummy_counter") -} - -func TestMetrics_Prom(t *testing.T) { - m, err := New(Config{ - MetricsSink: MetricSinkInMem, - Enabled: true, - EnableHostname: false, - ServiceName: "test", - PrometheusRetentionTime: 60, - EnableHostnameLabel: false, - }) - require.NoError(t, err) - require.NotNil(t, m) - require.True(t, m.prometheusEnabled) - - emitMetrics() - - gr, err := m.Gather(FormatPrometheus) - require.NoError(t, err) - require.Equal(t, gr.ContentType, string(ContentTypeText)) - - require.True(t, strings.Contains(string(gr.Metrics), "test_dummy_counter 30")) -} - -func emitMetrics() { - ticker := time.NewTicker(time.Second) - timeout := time.After(30 * time.Second) - - for { - select { - case <-ticker.C: - metrics.IncrCounter([]string{"dummy_counter"}, 1.0) - case <-timeout: - return - } - } -} diff --git a/telemetry/testing.go b/telemetry/testing.go new file mode 100644 index 000000000000..623a7e93e7cf --- /dev/null +++ b/telemetry/testing.go @@ -0,0 +1,27 @@ +package telemetry + +import ( + "context" + "fmt" + "os" + "testing" +) + +// TestingMain should be used in tests where you want to run telemetry and need clean shutdown +// behavior at the end of the test, for instance to collect benchmark metrics. +// If ctx is nil, context.Background() is used. +// Example: +// +// func TestMain(m *testing.M) { +// telemetry.TestingMain(m, nil) +// } +func TestingMain(m *testing.M, ctx context.Context) { + code := m.Run() + if ctx == nil { + ctx = context.Background() + } + if err := Shutdown(ctx); err != nil { + fmt.Printf("failed to shutdown telemetry after test completion: %v\n", err) + } + os.Exit(code) +} diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go index 8445ed823830..1d28e8af4d73 100644 --- a/telemetry/wrapper.go +++ b/telemetry/wrapper.go @@ -1,11 +1,16 @@ package telemetry import ( + "sync" "time" "github.com/hashicorp/go-metrics" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" ) +var globalLabels []metrics.Label + // Common metric key constants const ( MetricKeyPreBlocker = "pre_blocker" @@ -14,6 +19,17 @@ const ( MetricLabelNameModule = "module" ) +var meter = otel.Meter("cosmos-sdk") +var mtx sync.RWMutex +var counters map[string]metric.Float64Counter +var gauges map[string]metric.Float64Gauge +var histograms map[string]metric.Float64Histogram + +type Label struct { + Name string + Value string +} + // NewLabel creates a new instance of Label with name and value func NewLabel(name, value string) metrics.Label { return metrics.Label{Name: name, Value: value} diff --git a/telemetry/wrapper_test.go b/telemetry/wrapper_test.go deleted file mode 100644 index 5388839874bc..000000000000 --- a/telemetry/wrapper_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package telemetry - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -var mu sync.Mutex - -func initTelemetry(v bool) { - globalTelemetryEnabled = v -} - -// Reset the global state to a known disabled state before each test. -func setupTest(t *testing.T) { - t.Helper() - mu.Lock() // Ensure no other test can modify global state at the same time. - defer mu.Unlock() - initTelemetry(false) -} - -// TestNow tests the Now function when telemetry is enabled and disabled. -func TestNow(t *testing.T) { - setupTest(t) // Locks the mutex to avoid race condition. - - initTelemetry(true) - telemetryTime := Now() - assert.NotEqual(t, time.Time{}, telemetryTime, "Now() should not return zero time when telemetry is enabled") - - setupTest(t) // Reset the global state and lock the mutex again. - - initTelemetry(false) - telemetryTime = Now() - assert.Equal(t, time.Time{}, telemetryTime, "Now() should return zero time when telemetry is disabled") -} - -// TestIsTelemetryEnabled tests the IsTelemetryEnabled function. -func TestIsTelemetryEnabled(t *testing.T) { - setupTest(t) // Locks the mutex to avoid race condition. - - initTelemetry(true) - assert.True(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return true when globalTelemetryEnabled is set to true") - - setupTest(t) // Reset the global state and lock the mutex again. - - initTelemetry(false) - assert.False(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return false when globalTelemetryEnabled is set to false") -} diff --git a/tests/go.mod b/tests/go.mod index 9333bd85efb2..43bac5a8fa8a 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -44,7 +44,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -75,6 +74,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -100,6 +100,8 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -113,6 +115,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -130,8 +133,10 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -156,6 +161,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -170,16 +176,19 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -190,21 +199,42 @@ require ( github.com/supranational/blst v0.3.16 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/tests/go.sum b/tests/go.sum index 6d3701117192..42261c9ec25f 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -53,7 +53,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -161,6 +160,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -277,6 +278,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -342,6 +346,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -444,6 +451,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -452,6 +461,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -565,6 +576,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -687,6 +700,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -713,6 +728,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -720,8 +737,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -746,6 +763,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -808,6 +827,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -821,6 +844,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -836,25 +861,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -983,6 +1044,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -999,6 +1061,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1020,6 +1083,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 9e5b9858dcc5..58744d71760c 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -30,7 +30,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -38,6 +37,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -66,6 +66,8 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -76,6 +78,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -86,11 +89,14 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -111,6 +117,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.3 // indirect @@ -122,16 +129,19 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -145,15 +155,38 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index a1ce60b13e62..ed428bc46a3b 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -89,6 +88,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -197,6 +198,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -251,6 +255,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -344,6 +351,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -352,6 +361,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -454,6 +465,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -570,6 +583,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -596,6 +611,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -603,8 +620,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -629,6 +646,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -695,6 +714,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -706,6 +729,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -721,17 +746,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -844,6 +907,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -860,6 +924,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -878,6 +943,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/testutil/network/network.go b/testutil/network/network.go index 138b94bf77de..d4a0dfc9efee 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -25,11 +25,12 @@ import ( "google.golang.org/grpc" "cosmossdk.io/depinject" - "cosmossdk.io/log" sdkmath "cosmossdk.io/math" "cosmossdk.io/math/unsafe" pruningtypes "cosmossdk.io/store/pruning/types" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -377,7 +378,6 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { appCfg.MinGasPrices = cfg.MinGasPrices appCfg.API.Enable = true appCfg.API.Swagger = false - appCfg.Telemetry.Enabled = false ctx := server.NewDefaultContext() cmtCfg := ctx.Config diff --git a/types/config.go b/types/config.go index 1c61968bb06b..e4ef476e8e31 100644 --- a/types/config.go +++ b/types/config.go @@ -31,6 +31,7 @@ type Config struct { var ( configRegistry = make(map[string]*Config) registryMutex sync.Mutex + configKey string ) // getConfigKey returns a unique config scope identifier. @@ -40,6 +41,10 @@ func getConfigKey() string { return id } + if configKey != "" { + return configKey + } + exe, errExec := os.Executable() host, errHost := os.Hostname() pid := os.Getpid() @@ -51,7 +56,8 @@ func getConfigKey() string { host = "unknown-host" } - return fmt.Sprintf("%s|%s|%d", host, exe, pid) + configKey = fmt.Sprintf("%s|%s|%d", host, exe, pid) + return configKey } // NewConfig returns a new Config with default values. diff --git a/types/context.go b/types/context.go index 4e762cc15943..34f4a85cfd2f 100644 --- a/types/context.go +++ b/types/context.go @@ -6,12 +6,14 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/comet" "cosmossdk.io/core/header" - "cosmossdk.io/log" "cosmossdk.io/store/gaskv" storetypes "cosmossdk.io/store/types" + + "cosmossdk.io/log" ) // ExecMode defines the execution mode which can be set on a Context. @@ -439,6 +441,14 @@ func (c Context) WithIncarnationCache(cache map[string]any) Context { return c } +// StartSpan starts an otel span and returns a new context with the span attached. +// Use this instead of calling tracer.Start directly to have the span correctly +// attached to this context type. +func (c Context) StartSpan(tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) (Context, trace.Span) { + goCtx, span := tracer.Start(c.baseCtx, spanName, opts...) + return c.WithContext(goCtx), span +} + var ( _ context.Context = Context{} _ storetypes.Context = Context{} diff --git a/types/module/module.go b/types/module/module.go index b3b3cccb3774..457c6444ab74 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -39,6 +39,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/genesis" @@ -52,6 +54,10 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) +var ( + tracer = otel.Tracer("cosmos-sdk/types/module") +) + // AppModuleBasic is the standard form for basic non-dependent elements of an application module. type AppModuleBasic interface { HasName @@ -482,6 +488,10 @@ func (m *Manager) RegisterServices(cfg Configurator) error { // module must return a non-empty validator set update to correctly initialize // the chain. func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) (*abci.ResponseInitChain, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.InitGenesis") + defer span.End() + var validatorUpdates []abci.ValidatorUpdate ctx.Logger().Info("initializing blockchain state from genesis.json") for _, moduleName := range m.OrderInitGenesis { @@ -489,26 +499,27 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData continue } + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("InitGenesis.%s", moduleName)) mod := m.Modules[moduleName] // we might get an adapted module, a native core API module or a legacy module if module, ok := mod.(appmodule.HasGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) // core API genesis source, err := genesis.SourceFromRawJSON(genesisData[moduleName]) if err != nil { return &abci.ResponseInitChain{}, err } - err = module.InitGenesis(ctx, source) + err = module.InitGenesis(modCtx, source) if err != nil { return &abci.ResponseInitChain{}, err } } else if module, ok := mod.(HasGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) - module.InitGenesis(ctx, cdc, genesisData[moduleName]) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) + module.InitGenesis(modCtx, cdc, genesisData[moduleName]) } else if module, ok := mod.(HasABCIGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) - moduleValUpdates := module.InitGenesis(ctx, cdc, genesisData[moduleName]) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) + moduleValUpdates := module.InitGenesis(modCtx, cdc, genesisData[moduleName]) // use these validator updates if provided, the module manager assumes // only one module will update the validator set @@ -519,6 +530,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData validatorUpdates = moduleValUpdates } } + modSpan.End() } // a chain must initialize with a non-empty validator set @@ -754,10 +766,15 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver // It takes the current context as a parameter and returns a boolean value // indicating whether the migration was successfully executed or not. func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.PreBlock") + defer span.End() + paramsChanged := false for _, moduleName := range m.OrderPreBlockers { + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("PreBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasPreBlocker); ok { - rsp, err := module.PreBlock(ctx) + rsp, err := module.PreBlock(modCtx) if err != nil { return nil, err } @@ -765,6 +782,7 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { paramsChanged = true } } + modSpan.End() } return &sdk.ResponsePreBlock{ ConsensusParamsChanged: paramsChanged, @@ -775,13 +793,20 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { // child context with an event manager to aggregate events emitted from all // modules. func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.BeginBlock") + defer span.End() + ctx = ctx.WithEventManager(sdk.NewEventManager()) for _, moduleName := range m.OrderBeginBlockers { + var modSpan trace.Span + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("BeginBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok { - if err := module.BeginBlock(ctx); err != nil { + if err := module.BeginBlock(modCtx); err != nil { return sdk.BeginBlock{}, err } } + modSpan.End() } return sdk.BeginBlock{ @@ -793,17 +818,22 @@ func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { // child context with an event manager to aggregate events emitted from all // modules. func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.EndBlock") + defer span.End() + ctx = ctx.WithEventManager(sdk.NewEventManager()) validatorUpdates := []abci.ValidatorUpdate{} for _, moduleName := range m.OrderEndBlockers { + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("EndBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok { - err := module.EndBlock(ctx) + err := module.EndBlock(modCtx) if err != nil { return sdk.EndBlock{}, err } } else if module, ok := m.Modules[moduleName].(HasABCIEndBlock); ok { - moduleValUpdates, err := module.EndBlock(ctx) + moduleValUpdates, err := module.EndBlock(modCtx) if err != nil { return sdk.EndBlock{}, err } @@ -821,6 +851,7 @@ func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { } else { continue } + modSpan.End() } return sdk.EndBlock{ diff --git a/types/simulation/config.go b/types/simulation/config.go index 1e385fdfa1d6..b903ed3bc1f7 100644 --- a/types/simulation/config.go +++ b/types/simulation/config.go @@ -16,6 +16,7 @@ type Config struct { InitialBlockHeight int // initial block to start the simulation GenesisTime int64 // genesis time to start the simulation NumBlocks int // number of new blocks to simulate from the initial block height + NumRuns int // number of times to run the simulation for simulations that have multiple runs BlockSize int // operations per block ChainID string // chain-id used on the simulation diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000000..07704c9c62b0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1650 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "async-lru" +version = "2.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "cosmos-sdk" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "duckdb" }, + { name = "notebook" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "pydantic" }, + { name = "pytz" }, +] + +[package.metadata] +requires-dist = [ + { name = "duckdb", specifier = ">=1.4.1" }, + { name = "notebook", specifier = ">=7.4.7" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "plotly", specifier = ">=6.4.0" }, + { name = "pydantic", specifier = ">=2.12.3" }, + { name = "pytz", specifier = ">=2025.2" }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "duckdb" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/e7/21cf50a3d52ffceee1f0bcc3997fa96a5062e6bab705baee4f6c4e33cce5/duckdb-1.4.1.tar.gz", hash = "sha256:f903882f045d057ebccad12ac69975952832edfe133697694854bb784b8d6c76", size = 18461687, upload-time = "2025-10-07T10:37:28.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/08/705988c33e38665c969f7876b3ca4328be578554aa7e3dc0f34158da3e64/duckdb-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46496a2518752ae0c6c5d75d4cdecf56ea23dd098746391176dd8e42cf157791", size = 29077070, upload-time = "2025-10-07T10:36:59.83Z" }, + { url = "https://files.pythonhosted.org/packages/99/c5/7c9165f1e6b9069441bcda4da1e19382d4a2357783d37ff9ae238c5c41ac/duckdb-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c65ae7e9b541cea07d8075343bcfebdecc29a3c0481aa6078ee63d51951cfcd", size = 16167506, upload-time = "2025-10-07T10:37:02.24Z" }, + { url = "https://files.pythonhosted.org/packages/38/46/267f4a570a0ee3ae6871ddc03435f9942884284e22a7ba9b7cb252ee69b6/duckdb-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:598d1a314e34b65d9399ddd066ccce1eeab6a60a2ef5885a84ce5ed62dbaf729", size = 13762330, upload-time = "2025-10-07T10:37:04.581Z" }, + { url = "https://files.pythonhosted.org/packages/15/7b/c4f272a40c36d82df20937d93a1780eb39ab0107fe42b62cba889151eab9/duckdb-1.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2f16b8def782d484a9f035fc422bb6f06941ed0054b4511ddcdc514a7fb6a75", size = 18504687, upload-time = "2025-10-07T10:37:06.991Z" }, + { url = "https://files.pythonhosted.org/packages/17/fc/9b958751f0116d7b0406406b07fa6f5a10c22d699be27826d0b896f9bf51/duckdb-1.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a7d0aed068a5c33622a8848857947cab5cfb3f2a315b1251849bac2c74c492", size = 20513823, upload-time = "2025-10-07T10:37:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460, upload-time = "2025-10-07T10:37:12.154Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "9.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "json5" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, +] + +[[package]] +name = "jupyterlab" +version = "4.4.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/5d/75c42a48ff5fc826a7dff3fe4004cda47c54f9d981c351efacfbc9139d3c/jupyterlab-4.4.10.tar.gz", hash = "sha256:521c017508af4e1d6d9d8a9d90f47a11c61197ad63b2178342489de42540a615", size = 22969303, upload-time = "2025-10-22T14:50:58.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl", hash = "sha256:65939ab4c8dcd0c42185c2d0d1a9d60b254dc8c46fc4fdb286b63c51e9358e07", size = 12293385, upload-time = "2025-10-22T14:50:54.075Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mistune" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, +] + +[[package]] +name = "narwhals" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/dc/8db74daf8c2690ec696c1d772a33cc01511559ee8a9e92d7ed85a18e3c22/narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354", size = 584954, upload-time = "2025-11-04T16:36:42.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/a9/9e02fa97e421a355fc5e818e9c488080fce04a8e0eebb3ed75a84f041c4a/narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43", size = 419573, upload-time = "2025-11-04T16:36:40.574Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "notebook" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/09/f6f64ba156842ef68d3ea763fa171a2f7e7224f200a15dd4af5b83c34756/notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a", size = 13937702, upload-time = "2025-09-27T08:00:22.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/d7/06d13087e20388926e7423d2489e728d2e59f2453039cdb0574a7c070e76/notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6", size = 14342894, upload-time = "2025-09-27T08:00:18.496Z" }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "plotly" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/e6/b768650072837505804bed4790c5449ba348a3b720e27ca7605414e998cd/plotly-6.4.0.tar.gz", hash = "sha256:68c6db2ed2180289ef978f087841148b7efda687552276da15a6e9b92107052a", size = 7012379, upload-time = "2025-11-04T17:59:26.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/ae/89b45ccccfeebc464c9233de5675990f75241b8ee4cd63227800fdf577d1/plotly-6.4.0-py3-none-any.whl", hash = "sha256:a1062eafbdc657976c2eedd276c90e184ccd6c21282a5e9ee8f20efca9c9a4c5", size = 9892458, upload-time = "2025-11-04T17:59:22.622Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, + { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, + { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/03/ce566d92611dfac0085c2f4b048cd53ed7c274a5c05974b882a908d540a2/rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b", size = 366235, upload-time = "2025-10-22T22:22:28.397Z" }, + { url = "https://files.pythonhosted.org/packages/00/34/1c61da1b25592b86fd285bd7bd8422f4c9d748a7373b46126f9ae792a004/rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a", size = 348241, upload-time = "2025-10-22T22:22:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/ed1e28616848c61c493a067779633ebf4b569eccaacf9ccbdc0e7cba2b9d/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa", size = 378079, upload-time = "2025-10-22T22:22:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/ccb30333a16a470091b6e50289adb4d3ec656fd9951ba8c5e3aaa0746a67/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724", size = 393151, upload-time = "2025-10-22T22:22:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d0/73e2217c3ee486d555cb84920597480627d8c0240ff3062005c6cc47773e/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491", size = 517520, upload-time = "2025-10-22T22:22:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/23efe81c700427d0841a4ae7ea23e305654381831e6029499fe80be8a071/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399", size = 408699, upload-time = "2025-10-22T22:22:36.584Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/a324d3198da151820a326c1f988caaa4f37fc27955148a76fff7a2d787a9/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6", size = 385720, upload-time = "2025-10-22T22:22:38.014Z" }, + { url = "https://files.pythonhosted.org/packages/19/ad/e68120dc05af8b7cab4a789fccd8cdcf0fe7e6581461038cc5c164cd97d2/rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d", size = 401096, upload-time = "2025-10-22T22:22:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/99/90/c1e070620042459d60df6356b666bb1f62198a89d68881816a7ed121595a/rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb", size = 411465, upload-time = "2025-10-22T22:22:41.395Z" }, + { url = "https://files.pythonhosted.org/packages/68/61/7c195b30d57f1b8d5970f600efee72a4fad79ec829057972e13a0370fd24/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41", size = 558832, upload-time = "2025-10-22T22:22:42.871Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3d/06f3a718864773f69941d4deccdf18e5e47dd298b4628062f004c10f3b34/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7", size = 583230, upload-time = "2025-10-22T22:22:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/66/df/62fc783781a121e77fee9a21ead0a926f1b652280a33f5956a5e7833ed30/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9", size = 553268, upload-time = "2025-10-22T22:22:46.441Z" }, + { url = "https://files.pythonhosted.org/packages/84/85/d34366e335140a4837902d3dea89b51f087bd6a63c993ebdff59e93ee61d/rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5", size = 217100, upload-time = "2025-10-22T22:22:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1c/f25a3f3752ad7601476e3eff395fe075e0f7813fbb9862bd67c82440e880/rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e", size = 227759, upload-time = "2025-10-22T22:22:50.219Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d6/5f39b42b99615b5bc2f36ab90423ea404830bdfee1c706820943e9a645eb/rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1", size = 217326, upload-time = "2025-10-22T22:22:51.647Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8b/0c69b72d1cee20a63db534be0df271effe715ef6c744fdf1ff23bb2b0b1c/rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c", size = 355736, upload-time = "2025-10-22T22:22:53.211Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/0c2ee773cfb55c31a8514d2cece856dd299170a49babd50dcffb15ddc749/rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa", size = 342677, upload-time = "2025-10-22T22:22:54.723Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1c/22513ab25a27ea205144414724743e305e8153e6abe81833b5e678650f5a/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b", size = 371847, upload-time = "2025-10-22T22:22:56.295Z" }, + { url = "https://files.pythonhosted.org/packages/60/07/68e6ccdb4b05115ffe61d31afc94adef1833d3a72f76c9632d4d90d67954/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d", size = 381800, upload-time = "2025-10-22T22:22:57.808Z" }, + { url = "https://files.pythonhosted.org/packages/73/bf/6d6d15df80781d7f9f368e7c1a00caf764436518c4877fb28b029c4624af/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe", size = 518827, upload-time = "2025-10-22T22:22:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d3/2decbb2976cc452cbf12a2b0aaac5f1b9dc5dd9d1f7e2509a3ee00421249/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a", size = 399471, upload-time = "2025-10-22T22:23:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/f30892f9e54bd02e5faca3f6a26d6933c51055e67d54818af90abed9748e/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc", size = 377578, upload-time = "2025-10-22T22:23:03.52Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5d/3bce97e5534157318f29ac06bf2d279dae2674ec12f7cb9c12739cee64d8/rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259", size = 390482, upload-time = "2025-10-22T22:23:05.391Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f0/886bd515ed457b5bd93b166175edb80a0b21a210c10e993392127f1e3931/rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a", size = 402447, upload-time = "2025-10-22T22:23:06.93Z" }, + { url = "https://files.pythonhosted.org/packages/42/b5/71e8777ac55e6af1f4f1c05b47542a1eaa6c33c1cf0d300dca6a1c6e159a/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f", size = 552385, upload-time = "2025-10-22T22:23:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cb/6ca2d70cbda5a8e36605e7788c4aa3bea7c17d71d213465a5a675079b98d/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37", size = 575642, upload-time = "2025-10-22T22:23:10.348Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d4/407ad9960ca7856d7b25c96dcbe019270b5ffdd83a561787bc682c797086/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712", size = 544507, upload-time = "2025-10-22T22:23:12.434Z" }, + { url = "https://files.pythonhosted.org/packages/51/31/2f46fe0efcac23fbf5797c6b6b7e1c76f7d60773e525cb65fcbc582ee0f2/rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342", size = 205376, upload-time = "2025-10-22T22:23:13.979Z" }, + { url = "https://files.pythonhosted.org/packages/92/e4/15947bda33cbedfc134490a41841ab8870a72a867a03d4969d886f6594a2/rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907", size = 215907, upload-time = "2025-10-22T22:23:15.5Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/ffe8cd7a6a02833b10623bf765fbb57ce977e9a4318ca0e8cf97e9c3d2b3/rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472", size = 353830, upload-time = "2025-10-22T22:23:17.03Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9f/890f36cbd83a58491d0d91ae0db1702639edb33fb48eeb356f80ecc6b000/rpds_py-0.28.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2", size = 341819, upload-time = "2025-10-22T22:23:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/09/e3/921eb109f682aa24fb76207698fbbcf9418738f35a40c21652c29053f23d/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527", size = 373127, upload-time = "2025-10-22T22:23:20.216Z" }, + { url = "https://files.pythonhosted.org/packages/23/13/bce4384d9f8f4989f1a9599c71b7a2d877462e5fd7175e1f69b398f729f4/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733", size = 382767, upload-time = "2025-10-22T22:23:21.787Z" }, + { url = "https://files.pythonhosted.org/packages/23/e1/579512b2d89a77c64ccef5a0bc46a6ef7f72ae0cf03d4b26dcd52e57ee0a/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56", size = 517585, upload-time = "2025-10-22T22:23:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/62/3c/ca704b8d324a2591b0b0adcfcaadf9c862375b11f2f667ac03c61b4fd0a6/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8", size = 399828, upload-time = "2025-10-22T22:23:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/da/37/e84283b9e897e3adc46b4c88bb3f6ec92a43bd4d2f7ef5b13459963b2e9c/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370", size = 375509, upload-time = "2025-10-22T22:23:27.32Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c2/a980beab869d86258bf76ec42dec778ba98151f253a952b02fe36d72b29c/rpds_py-0.28.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d", size = 392014, upload-time = "2025-10-22T22:23:29.332Z" }, + { url = "https://files.pythonhosted.org/packages/da/b5/b1d3c5f9d3fa5aeef74265f9c64de3c34a0d6d5cd3c81c8b17d5c8f10ed4/rpds_py-0.28.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728", size = 402410, upload-time = "2025-10-22T22:23:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/74/ae/cab05ff08dfcc052afc73dcb38cbc765ffc86f94e966f3924cd17492293c/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01", size = 553593, upload-time = "2025-10-22T22:23:32.834Z" }, + { url = "https://files.pythonhosted.org/packages/70/80/50d5706ea2a9bfc9e9c5f401d91879e7c790c619969369800cde202da214/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515", size = 576925, upload-time = "2025-10-22T22:23:34.47Z" }, + { url = "https://files.pythonhosted.org/packages/ab/12/85a57d7a5855a3b188d024b099fd09c90db55d32a03626d0ed16352413ff/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e", size = 542444, upload-time = "2025-10-22T22:23:36.093Z" }, + { url = "https://files.pythonhosted.org/packages/6c/65/10643fb50179509150eb94d558e8837c57ca8b9adc04bd07b98e57b48f8c/rpds_py-0.28.0-cp314-cp314-win32.whl", hash = "sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f", size = 207968, upload-time = "2025-10-22T22:23:37.638Z" }, + { url = "https://files.pythonhosted.org/packages/b4/84/0c11fe4d9aaea784ff4652499e365963222481ac647bcd0251c88af646eb/rpds_py-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1", size = 218876, upload-time = "2025-10-22T22:23:39.179Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/3ab3b86ded7bb18478392dc3e835f7b754cd446f62f3fc96f4fe2aca78f6/rpds_py-0.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d", size = 212506, upload-time = "2025-10-22T22:23:40.755Z" }, + { url = "https://files.pythonhosted.org/packages/51/ec/d5681bb425226c3501eab50fc30e9d275de20c131869322c8a1729c7b61c/rpds_py-0.28.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b", size = 355433, upload-time = "2025-10-22T22:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/568c5e689e1cfb1ea8b875cffea3649260955f677fdd7ddc6176902d04cd/rpds_py-0.28.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a", size = 342601, upload-time = "2025-10-22T22:23:44.372Z" }, + { url = "https://files.pythonhosted.org/packages/32/fe/51ada84d1d2a1d9d8f2c902cfddd0133b4a5eb543196ab5161d1c07ed2ad/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592", size = 372039, upload-time = "2025-10-22T22:23:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/07/c1/60144a2f2620abade1a78e0d91b298ac2d9b91bc08864493fa00451ef06e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba", size = 382407, upload-time = "2025-10-22T22:23:48.098Z" }, + { url = "https://files.pythonhosted.org/packages/45/ed/091a7bbdcf4038a60a461df50bc4c82a7ed6d5d5e27649aab61771c17585/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c", size = 518172, upload-time = "2025-10-22T22:23:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/54/dd/02cc90c2fd9c2ef8016fd7813bfacd1c3a1325633ec8f244c47b449fc868/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91", size = 399020, upload-time = "2025-10-22T22:23:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/81/5d98cc0329bbb911ccecd0b9e19fbf7f3a5de8094b4cda5e71013b2dd77e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed", size = 377451, upload-time = "2025-10-22T22:23:53.711Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/4d5bcd49e3dfed2d38e2dcb49ab6615f2ceb9f89f5a372c46dbdebb4e028/rpds_py-0.28.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b", size = 390355, upload-time = "2025-10-22T22:23:55.299Z" }, + { url = "https://files.pythonhosted.org/packages/3f/79/9f14ba9010fee74e4f40bf578735cfcbb91d2e642ffd1abe429bb0b96364/rpds_py-0.28.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e", size = 403146, upload-time = "2025-10-22T22:23:56.929Z" }, + { url = "https://files.pythonhosted.org/packages/39/4c/f08283a82ac141331a83a40652830edd3a4a92c34e07e2bbe00baaea2f5f/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1", size = 552656, upload-time = "2025-10-22T22:23:58.62Z" }, + { url = "https://files.pythonhosted.org/packages/61/47/d922fc0666f0dd8e40c33990d055f4cc6ecff6f502c2d01569dbed830f9b/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c", size = 576782, upload-time = "2025-10-22T22:24:00.312Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0c/5bafdd8ccf6aa9d3bfc630cfece457ff5b581af24f46a9f3590f790e3df2/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092", size = 544671, upload-time = "2025-10-22T22:24:02.297Z" }, + { url = "https://files.pythonhosted.org/packages/2c/37/dcc5d8397caa924988693519069d0beea077a866128719351a4ad95e82fc/rpds_py-0.28.0-cp314-cp314t-win32.whl", hash = "sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3", size = 205749, upload-time = "2025-10-22T22:24:03.848Z" }, + { url = "https://files.pythonhosted.org/packages/d7/69/64d43b21a10d72b45939a28961216baeb721cc2a430f5f7c3bfa21659a53/rpds_py-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578", size = 216233, upload-time = "2025-10-22T22:24:05.471Z" }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] diff --git a/x/gov/testutil/expected_keepers_mocks.go b/x/gov/testutil/expected_keepers_mocks.go index 3c5b098bb417..d04379a92ef9 100644 --- a/x/gov/testutil/expected_keepers_mocks.go +++ b/x/gov/testutil/expected_keepers_mocks.go @@ -16,12 +16,13 @@ import ( address "cosmossdk.io/core/address" math "cosmossdk.io/math" types "cosmossdk.io/store/types" + gomock "go.uber.org/mock/gomock" + types0 "github.com/cosmos/cosmos-sdk/types" query "github.com/cosmos/cosmos-sdk/types/query" keeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" types1 "github.com/cosmos/cosmos-sdk/x/bank/types" types2 "github.com/cosmos/cosmos-sdk/x/staking/types" - gomock "go.uber.org/mock/gomock" ) // MockAccountKeeper is a mock of AccountKeeper interface. diff --git a/x/simulation/client/cli/flags.go b/x/simulation/client/cli/flags.go index 8479b83f5e1c..c8552fec91bc 100644 --- a/x/simulation/client/cli/flags.go +++ b/x/simulation/client/cli/flags.go @@ -20,6 +20,7 @@ var ( FlagSeedValue int64 FlagInitialBlockHeightValue int FlagNumBlocksValue int + FlagNumRunsValue int FlagBlockSizeValue int FlagLeanValue bool FlagCommitValue bool @@ -51,6 +52,7 @@ func GetSimulatorFlags() { flag.StringVar(&FlagExportStatsPathValue, "ExportStatsPath", "", "custom file path to save the exported simulation statistics JSON") flag.Int64Var(&FlagSeedValue, "Seed", DefaultSeedValue, "simulation random seed") flag.IntVar(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation") + flag.IntVar(&FlagNumRunsValue, "NumRuns", 3, "number of runs to run for simulations that do multiple runs at once") flag.IntVar(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height") flag.IntVar(&FlagBlockSizeValue, "BlockSize", 200, "operations per block") flag.BoolVar(&FlagLeanValue, "Lean", false, "lean simulation log output") @@ -81,6 +83,7 @@ func NewConfigFromFlags() simulation.Config { Seed: FlagSeedValue, InitialBlockHeight: FlagInitialBlockHeightValue, GenesisTime: FlagGenesisTimeValue, + NumRuns: FlagNumRunsValue, NumBlocks: FlagNumBlocksValue, BlockSize: FlagBlockSizeValue, Lean: FlagLeanValue,