diff --git a/README.md b/README.md index 855b7d7..d262bd7 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ # clp-bench -clp-bench is a tool for benchmarking [CLP] as well as other log management tools. The tool itself is -a Python package, and we also provide a [web interface][ui] for viewing results. -The methodology for the benchmarks is described [here](docs/methodology.md). +**clp-bench** is a tool for benchmarking [glt] and other log management systems. It functions as a +Python package and includes a [web interface][ui] for displaying benchmark results. ## Requirements -* Docker -* Python v3.10 or higher +- Docker +- Python v3.10 or higher -# Set up +# Setup ```shell python3 -m venv venv @@ -17,7 +16,39 @@ python3 -m venv venv pip install -e . ``` -You can use `clp-bench --help` to see usage instructions. +To view usage instructions, run `clp-bench --help`. -[CLP]: https://github.com/y-scope/clp -[ui]: ui \ No newline at end of file +# Results +You can view the current benchmark results [here][webui]. The benchmark currently evaluates +ingestion and query performance for the following tools: + +| Tool | Version | +|--------------------------------|-------------| +| [ClickHouse][clickhouse] | 23.3.1.2823 | +| [glt][glt] | 0.2.0 | +| [clp-s][clp-s] | 0.2.0 | +| [Elasticsearch][elasticsearch] | 8.6.2 | +| `grep` | 3.7 | +| [Loki][loki] | 3.0.0 | +| [MongoDB][mongodb] | 6.0.19 | +| [Splunk][splunk] | 9.3.2 | + +Don't see a tool here? Feel free to file a [GitHub issue][new-issue] for one or follow this +[guide][adding-a-tool] for how to add one. + +For a detailed description of the benchmarking +methodology, see [here][methodology]. + +[assets]: assets +[clickhouse]: https://clickhouse.com/ +[glt]: https://github.com/y-scope/clp +[clp-s]: https://docs.yscope.com/clp/main/user-guide/core-clp-s.html +[webui]: https://benchmarks.yscope.com/clp/ +[elasticsearch]: https://www.elastic.co/downloads/elasticsearch +[loki]: https://grafana.com/oss/loki/ +[mongodb]: https://www.mongodb.com/ +[methodology]: docs/methodology.md +[new-issue]: https://github.com/y-scope/clp-bench/issues/new +[adding-a-tool]: docs/adding-a-tool.md +[splunk]: https://www.splunk.com/ +[ui]: ui diff --git a/assets/dynamically-structured/clickhouse/Dockerfile b/assets/dynamically-structured/clickhouse/Dockerfile new file mode 100644 index 0000000..0d1afde --- /dev/null +++ b/assets/dynamically-structured/clickhouse/Dockerfile @@ -0,0 +1 @@ +FROM clickhouse/clickhouse-server:latest diff --git a/assets/dynamically-structured/clickhouse/clear-cache.sh b/assets/dynamically-structured/clickhouse/clear-cache.sh new file mode 100755 index 0000000..8eaf790 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/clear-cache.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +clickhouse-client --query "SYSTEM DROP UNCOMPRESSED CACHE" +clickhouse-client --query "SYSTEM DROP MARK CACHE" +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/dynamically-structured/clickhouse/config.yaml b/assets/dynamically-structured/clickhouse/config.yaml new file mode 100644 index 0000000..26712b4 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/config.yaml @@ -0,0 +1,29 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: clickhouse-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets/mongod.log +hot_run_warm_up_times: 3 +related_processes: + - clickhouse-server + - clickhouse-client + - clickhouse-watchd +queries: + - '"JSON_EXISTS(raw, ''$.attr.tickets'')"' + - '"JSONExtractInt(raw, ''id'') = 22419"' + - > + "JSON_VALUE(raw, '$.attr.message.msg') like 'log_release%' AND JSON_VALUE(raw, '$.attr. + message.session_name') = 'connection'" + - > + "JSON_VALUE(raw, '$.ctx') = 'initandlisten' AND (JSON_VALUE(raw, '$.attr.message.msg') + like 'log_remove%' OR JSON_VALUE(raw, '$.msg') != 'WiredTiger message')" + - > + "JSON_VALUE(raw, '$.c') = 'WTWRTLOG' and JSONExtractInt(JSONExtractString( + JSONExtractString(raw, 'attr'), 'message'), 'ts_sec') > 1679490000" + - > + "JSON_VALUE(raw, '$.ctx') = 'FlowControlRefresher' AND JSONExtractInt(JSONExtractString( + raw, 'attr'), 'numTrimmed') = 0 " diff --git a/assets/dynamically-structured/clickhouse/container-name b/assets/dynamically-structured/clickhouse/container-name new file mode 100644 index 0000000..9153027 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/container-name @@ -0,0 +1 @@ +clickhouse-clp-bench \ No newline at end of file diff --git a/assets/dynamically-structured/clickhouse/docker-build.sh b/assets/dynamically-structured/clickhouse/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/dynamically-structured/clickhouse/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/dynamically-structured/clickhouse/docker-run.sh b/assets/dynamically-structured/clickhouse/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/dynamically-structured/clickhouse/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/dynamically-structured/clickhouse/ingest.sh b/assets/dynamically-structured/clickhouse/ingest.sh new file mode 100755 index 0000000..ec74f78 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/ingest.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +collection_name=clickhouse_clp_bench + +clickhouse-client \ + --max_threads 1 \ + --query "INSERT INTO ${collection_name} FROM INFILE '$1' FORMAT JSONAsString" \ + >/dev/null 2>&1 diff --git a/assets/dynamically-structured/clickhouse/launch.sh b/assets/dynamically-structured/clickhouse/launch.sh new file mode 100755 index 0000000..4698ace --- /dev/null +++ b/assets/dynamically-structured/clickhouse/launch.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Start the ClickHouse server in daemon mode +clickhouse-server --daemon + +# Wait until ClickHouse server is running +while [ "$(clickhouse-client --query "SELECT 1" 2>/dev/null)" != "1" ]; do + sleep 1 +done diff --git a/assets/dynamically-structured/clickhouse/measure-compressed-size.sh b/assets/dynamically-structured/clickhouse/measure-compressed-size.sh new file mode 100755 index 0000000..bb8e3a5 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/measure-compressed-size.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +collection_name=clickhouse_clp_bench + +clickhouse-client \ + --query "SELECT SUM(bytes) FROM system.parts WHERE active AND table = '${collection_name}'" diff --git a/assets/dynamically-structured/clickhouse/measure-decompressed-size.sh b/assets/dynamically-structured/clickhouse/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/dynamically-structured/clickhouse/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/clickhouse/methodology.md b/assets/dynamically-structured/clickhouse/methodology.md new file mode 100644 index 0000000..68bf212 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/methodology.md @@ -0,0 +1,25 @@ +# ClickHouse methodology + +## Basics + +Version: [23.3.1.2823][download] + +## Setup + +We start the ClickHouse server in daemon mode. + +## Specifics + +To store JSON records, we use a +[single string field][jsonasstring] in ClickHouse, eliminating the need for preprocessing. + +For query benchmarking, we operate in [single-thread mode][max_threads] by setting +`max_threads = 1`. Additionally, we configure the +[minimum data volume required for direct I/O access][direct_io] to 1 byte +(`min_bytes_to_use_direct_io = 1`) on the storage disk. + + +[download]: https://hub.docker.com/layers/clickhouse/clickhouse-server/23.3.1.2823/images/sha256-b88fd8c71b64d3158751337557ff089ff7b0d1ebf81d9c4c7aa1f0b37a31ee64?context=explore +[direct_io]: https://clickhouse.com/docs/en/operations/settings/settings#min_bytes_to_use_direct_io +[jsonasstring]: https://clickhouse.com/docs/en/interfaces/formats#jsonasstring +[max_threads]: https://clickhouse.com/docs/en/operations/settings/settings#max_threads diff --git a/assets/dynamically-structured/clickhouse/reset.sh b/assets/dynamically-structured/clickhouse/reset.sh new file mode 100755 index 0000000..79e2861 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/reset.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +collection_name=clickhouse_clp_bench + +clickhouse-client \ + --max_threads 1 \ + --query "DROP TABLE IF EXISTS ${collection_name}" >/dev/null 2>&1 +clickhouse-client \ + --max_threads 1 \ + --query "CREATE TABLE ${collection_name}(raw String CODEC(ZSTD(3))) ENGINE = MergeTree ORDER \ + BY tuple()" >/dev/null 2>&1 diff --git a/assets/dynamically-structured/clickhouse/results.json b/assets/dynamically-structured/clickhouse/results.json new file mode 100644 index 0000000..b25e412 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/results.json @@ -0,0 +1,36 @@ +{ + "target": "clickhouse", + "targetDisplayedName": "ClickHouse", + "displayedOrder": 3, + "isEnable": true, + "type": 2, + "ingestTime": 636409, + "compressedSize": 1050348093, + "avgIngestMem": 39008949248, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 707801088, + "queryTimes": [ + 189293, + 117725, + 199560, + 226136, + 221677, + 219761 + ] + }, + { + "metric": 2, + "avgQueryMem": 603047936, + "queryTimes": [ + 189381, + 115290, + 198320, + 225983, + 227425, + 224492 + ] + } + ] +} diff --git a/assets/dynamically-structured/clickhouse/search.sh b/assets/dynamically-structured/clickhouse/search.sh new file mode 100755 index 0000000..6496584 --- /dev/null +++ b/assets/dynamically-structured/clickhouse/search.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +collection_name=clickhouse_clp_bench + +clickhouse-client \ + --max_threads 1 \ + --query "SELECT * from ${collection_name} where $1 SETTINGS max_threads = 1, \ + min_bytes_to_use_direct_io = 1" 2>/dev/null | wc -l diff --git a/assets/dynamically-structured/clickhouse/terminate.sh b/assets/dynamically-structured/clickhouse/terminate.sh new file mode 100755 index 0000000..c8a938d --- /dev/null +++ b/assets/dynamically-structured/clickhouse/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +/etc/init.d/clickhouse-server stop >/dev/null 2>&1 diff --git a/assets/dynamically-structured/clp-s/Dockerfile b/assets/dynamically-structured/clp-s/Dockerfile new file mode 100644 index 0000000..6fd60f0 --- /dev/null +++ b/assets/dynamically-structured/clp-s/Dockerfile @@ -0,0 +1,11 @@ +FROM ghcr.io/y-scope/clp/clp-core-dependencies-x86-ubuntu-jammy:main + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + htop \ + jq \ + python3-venv \ + rsync \ + sqlite3 \ + tmux \ + vim diff --git a/assets/dynamically-structured/clp-s/clear-cache.sh b/assets/dynamically-structured/clp-s/clear-cache.sh new file mode 100755 index 0000000..78387c4 --- /dev/null +++ b/assets/dynamically-structured/clp-s/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/dynamically-structured/clp-s/config.yaml b/assets/dynamically-structured/clp-s/config.yaml new file mode 100644 index 0000000..8913093 --- /dev/null +++ b/assets/dynamically-structured/clp-s/config.yaml @@ -0,0 +1,19 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: clp-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets/mongod.log +hot_run_warm_up_times: 3 +related_processes: + - /home/assets/clp-s +queries: + - '"attr.tickets:*"' + - '"id: 22419"' + - '"attr.message.msg: log_release* AND attr.message.session_name: connection"' + - '''ctx: initandlisten AND (NOT msg: "WiredTigermessage" OR attr.message.msg: log_remove*)''' + - '"c: WTWRTLOG AND attr.message.ts_sec > 1679490000"' + - '"ctx: FlowControlRefresher AND attr.numTrimmed: 0"' diff --git a/assets/dynamically-structured/clp-s/container-name b/assets/dynamically-structured/clp-s/container-name new file mode 100644 index 0000000..45bfecc --- /dev/null +++ b/assets/dynamically-structured/clp-s/container-name @@ -0,0 +1 @@ +clp-clp-bench \ No newline at end of file diff --git a/assets/dynamically-structured/clp-s/docker-build.sh b/assets/dynamically-structured/clp-s/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/dynamically-structured/clp-s/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/dynamically-structured/clp-s/docker-run.sh b/assets/dynamically-structured/clp-s/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/dynamically-structured/clp-s/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/dynamically-structured/clp-s/ingest.sh b/assets/dynamically-structured/clp-s/ingest.sh new file mode 100755 index 0000000..3971ffb --- /dev/null +++ b/assets/dynamically-structured/clp-s/ingest.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +clp_s_binary=/home/assets/clp-s +data_path=/home/archives + +"${clp_s_binary}" c --timestamp-key "t.\$date" --target-encoded-size 268435456 "$data_path" "$1" diff --git a/assets/dynamically-structured/clp-s/launch.sh b/assets/dynamically-structured/clp-s/launch.sh new file mode 100755 index 0000000..a1ef9e1 --- /dev/null +++ b/assets/dynamically-structured/clp-s/launch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +data_path=/home/archives + +mkdir -p "$data_path" diff --git a/assets/dynamically-structured/clp-s/measure-compressed-size.sh b/assets/dynamically-structured/clp-s/measure-compressed-size.sh new file mode 100755 index 0000000..fadfe5a --- /dev/null +++ b/assets/dynamically-structured/clp-s/measure-compressed-size.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +data_path=/home/archives +du "$data_path" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/clp-s/measure-decompressed-size.sh b/assets/dynamically-structured/clp-s/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/dynamically-structured/clp-s/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/clp-s/methodology.md b/assets/dynamically-structured/clp-s/methodology.md new file mode 100644 index 0000000..cb908ee --- /dev/null +++ b/assets/dynamically-structured/clp-s/methodology.md @@ -0,0 +1,21 @@ +# clp-s methodology + +## Basics + +Version: [0.2.0][download] + +## Setup + +First, download the latest released binary, or clone the latest [code][clp] and compile it locally +by following these [instructions][core-build]. Then we use `clp-s` binary. + +## Specifics + +The setup for CLP-S is almost the same as that of CLP. The only difference is the query format: JSON +log queries use [kql], as specified in the `queries` field of `config.yaml`. + + +[clp]: https://github.com/y-scope/clp +[core-build]: https://docs.yscope.com/clp/main/dev-guide/components-core/index.html +[download]: https://github.com/y-scope/clp/releases/tag/v0.2.0 +[kql]: https://docs.yscope.com/clp/main/user-guide/reference-json-search-syntax.html diff --git a/assets/dynamically-structured/clp-s/reset.sh b/assets/dynamically-structured/clp-s/reset.sh new file mode 100755 index 0000000..87f33bc --- /dev/null +++ b/assets/dynamically-structured/clp-s/reset.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +data_path=/home/archives + +rm -rf "$data_path" +mkdir -p "$data_path" diff --git a/assets/dynamically-structured/clp-s/results.json b/assets/dynamically-structured/clp-s/results.json new file mode 100644 index 0000000..8ec23d7 --- /dev/null +++ b/assets/dynamically-structured/clp-s/results.json @@ -0,0 +1,36 @@ +{ + "target": "clps", + "targetDisplayedName": "CLP-S", + "displayedOrder": 1, + "isEnable": true, + "type": 2, + "ingestTime": 786770, + "compressedSize": 381346120, + "avgIngestMem": 466773606, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 135224361, + "queryTimes": [ + 1260, + 28440, + 1180, + 1730, + 1410, + 1190 + ] + }, + { + "metric": 2, + "avgQueryMem": 164731290, + "queryTimes": [ + 1300, + 28560, + 1170, + 1700, + 1420, + 1210 + ] + } + ] +} diff --git a/assets/dynamically-structured/clp-s/search.sh b/assets/dynamically-structured/clp-s/search.sh new file mode 100755 index 0000000..d469616 --- /dev/null +++ b/assets/dynamically-structured/clp-s/search.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +clp_s_binary=/home/assets/clp-s +data_path=/home/archives + +"${clp_s_binary}" s "$data_path" "$1" | wc -l diff --git a/assets/dynamically-structured/clp-s/terminate.sh b/assets/dynamically-structured/clp-s/terminate.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/dynamically-structured/clp-s/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/elasticsearch/Dockerfile b/assets/dynamically-structured/elasticsearch/Dockerfile similarity index 61% rename from assets/elasticsearch/Dockerfile rename to assets/dynamically-structured/elasticsearch/Dockerfile index 8424848..e22a539 100644 --- a/assets/elasticsearch/Dockerfile +++ b/assets/dynamically-structured/elasticsearch/Dockerfile @@ -5,17 +5,20 @@ RUN apt-get update \ curl \ gpg -RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | tee /etc/apt/sources.list.d/elastic-8.x.list +RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch \ + | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg]" \ + "https://artifacts.elastic.co/packages/8.x/apt stable main" \ + | tee /etc/apt/sources.list.d/elastic-8.x.list RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ - python3-venv \ - tmux \ - vim \ + elasticsearch=8.6.2 \ libcurl4 \ libcurl4-openssl-dev \ - elasticsearch=8.6.2 \ - python3-pip + python3-pip \ + python3-venv \ + tmux \ + vim -RUN pip install elasticsearch requests numpy matplotlib +RUN pip install elasticsearch requests diff --git a/assets/dynamically-structured/elasticsearch/clear-cache.py b/assets/dynamically-structured/elasticsearch/clear-cache.py new file mode 100644 index 0000000..045a43e --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/clear-cache.py @@ -0,0 +1,9 @@ +import os + +from elasticsearch import Elasticsearch + +collection_name = "elasticsearch_clp_bench" + +es = Elasticsearch("http://localhost:9202", timeout=30, max_retries=10, retry_on_timeout=True) +es.indices.clear_cache(index=collection_name) +os.system("sync; echo 1 > /proc/sys/vm/drop_caches") diff --git a/assets/dynamically-structured/elasticsearch/clear-cache.sh b/assets/dynamically-structured/elasticsearch/clear-cache.sh new file mode 100755 index 0000000..8065590 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/clear-cache.py" diff --git a/assets/dynamically-structured/elasticsearch/config.yaml b/assets/dynamically-structured/elasticsearch/config.yaml new file mode 100644 index 0000000..2724e9b --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/config.yaml @@ -0,0 +1,30 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: elasticsearch-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets/mongod.log +hot_run_warm_up_times: 3 +related_processes: + - /usr/share/elasticsearch/jdk/bin/java + - /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller +queries: + - '''{"query": {"exists": {"field": "attr.tickets"}}, "size": 10000}''' + - '''{"query": {"term": {"id": 22419}}, "size": 10000}''' + - > + '{"query": {"bool": {"must": [{"wildcard": {"attr.message.msg": "log_release*"}}, {"match": { + "attr.message.session_name": "connection"}}]}}, "size": 10000}' + - > + '{"query": {"bool": {"must": [{"match": {"ctx": "initandlisten"}}], "should": [{"wildcard": { + "attr.message.msg": "log_remove*"}}, {"bool": {"must_not": [{"match_phrase": {"msg": + "WiredTiger message"}}]}}], "minimum_should_match": 1}}, "size": 10000}' + - > + '{"query": {"bool": {"must": [{"match": {"c": "WTWRTLOG"}}, {"range": {"attr.message.ts_sec": { + "gt": 1679490000}}}]}}, "size": 10000}' + - > + '{"query": {"bool": {"must": [{"match": {"ctx": "FlowControlRefresher"}}, { + "match": {"attr.numTrimmed": 0}}]}}, "size": 10000}' + \ No newline at end of file diff --git a/assets/dynamically-structured/elasticsearch/container-name b/assets/dynamically-structured/elasticsearch/container-name new file mode 100644 index 0000000..496abf7 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/container-name @@ -0,0 +1 @@ +elasticsearch-clp-bench \ No newline at end of file diff --git a/assets/dynamically-structured/elasticsearch/docker-build.sh b/assets/dynamically-structured/elasticsearch/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/dynamically-structured/elasticsearch/docker-run.sh b/assets/dynamically-structured/elasticsearch/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/dynamically-structured/elasticsearch/ingest.py b/assets/dynamically-structured/elasticsearch/ingest.py new file mode 100644 index 0000000..b2b4eef --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/ingest.py @@ -0,0 +1,93 @@ +import json +import logging +import sys + +import requests +from elasticsearch import Elasticsearch +from elasticsearch.helpers import streaming_bulk # type: ignore + +collection_name = "elasticsearch_clp_bench" + +log_path = sys.argv[1] +# log_path = '/home/muslope/mongodb-test/mongod.log.2023-03-22T03-45-46' +# log_path = '/home/muslope/collection_names/mongod.log' + + +def traverse_data(collection_name): + with open(log_path, encoding="utf-8") as f: + for line in f: + json_line = json.loads(line) + if "attr" in json_line: + attr = json_line["attr"] + if "uuid" in attr and isinstance(attr["uuid"], dict): + uuid = attr["uuid"]["uuid"]["$uuid"] + json_line["attr"]["uuid"] = uuid + if "error" in attr and isinstance(attr["error"], str): + error_msg = attr["error"] + json_line["attr"]["error"] = {} + json_line["attr"]["error"]["errmsg"] = error_msg + if "command" in attr: + command = attr["command"] + if isinstance(command, str): + json_line["attr"]["command"] = {} + json_line["attr"]["command"]["command"] = command + if ( + isinstance(command, dict) + and "q" in command + and isinstance(command["q"], dict) + and "_id" in command["q"] + and not isinstance(command["q"]["_id"], dict) + ): + id_value = str(command["q"]["_id"]) + json_line["attr"]["command"]["q"]["_id"] = {} + json_line["attr"]["command"]["q"]["_id"]["_ooid"] = id_value + if ( + "writeConcern" in attr + and isinstance(attr["writeConcern"], dict) + and "w" in attr["writeConcern"] + and isinstance(attr["writeConcern"]["w"], int) + ): + w = attr["writeConcern"]["w"] + json_line["attr"]["writeConcern"]["w"] = str(w) + if ( + "query" in attr + and isinstance(attr["query"], dict) + and "_id" in attr["query"] + and not isinstance(attr["query"]["_id"], dict) + ): + id_value = str(attr["query"]["_id"]) + json_line["attr"]["query"]["_id"] = {} + json_line["attr"]["query"]["_id"]["_ooid"] = id_value + yield { + "_index": collection_name, + "_source": json_line, + } + + +def ingest_dataset(): + es = Elasticsearch("http://localhost:9202", request_timeout=1200, retry_on_timeout=True) + + count = 0 + for success, info in streaming_bulk( + es, + traverse_data(collection_name), + raise_on_error=False, + raise_on_exception=False, + chunk_size=10000, + request_timeout=120, + ): + if success: + count += 1 + else: + logging.error(f"Failed to index document at {count}: {info}") + if count % 100000 == 0: + logging.info(f"Index {count} logs") + + requests.post(f"http://localhost:9202/{collection_name}/_flush/") + + +if __name__ == "__main__": + try: + ingest_dataset() + except Exception as e: + print(e) diff --git a/assets/dynamically-structured/elasticsearch/ingest.sh b/assets/dynamically-structured/elasticsearch/ingest.sh new file mode 100755 index 0000000..ce9d34e --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/ingest.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/ingest.py" "$1" diff --git a/assets/dynamically-structured/elasticsearch/launch.sh b/assets/dynamically-structured/elasticsearch/launch.sh new file mode 100755 index 0000000..ea6d4f1 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/launch.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +sed -i "s/#http.port: 9200/http.port: 9202/" /etc/elasticsearch/elasticsearch.yml +sed -i "s/xpack.security.enabled: true/xpack.security.enabled: false/" \ + /etc/elasticsearch/elasticsearch.yml +sed -i "/cluster.initial_master_nodes/d" /etc/elasticsearch/elasticsearch.yml +grep -q "discovery.type: single-node" /etc/elasticsearch/elasticsearch.yml \ + || echo "discovery.type: single-node" >>/etc/elasticsearch/elasticsearch.yml + +sed -i "/elasticsearch/ s/\/bin\/false/\/bin\/bash/" /etc/passwd +sed -i "/elasticsearch/ s/\/nonexistent/\/usr\/share\/elasticsearch/" /etc/passwd + +# It seems the environment variables sometimes does not work, need stop and start the elasticsearch +# in the container manually to ensure it is applied use to check: curl -XGET \ +# "http://localhost:9202/_nodes/stats/jvm?pretty" +# ES_JAVA_OPTS="-Xms256m -Xmx256m" su elasticsearch -c \ +# "/usr/share/elasticsearch/bin/elasticsearch -d" +su elasticsearch -c "/usr/share/elasticsearch/bin/elasticsearch -d" diff --git a/assets/dynamically-structured/elasticsearch/measure-compressed-size.py b/assets/dynamically-structured/elasticsearch/measure-compressed-size.py new file mode 100644 index 0000000..f36a216 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/measure-compressed-size.py @@ -0,0 +1,9 @@ +import time + +import requests + +collection_name = "elasticsearch_clp_bench" + +time.sleep(5) +response = requests.get(f"http://localhost:9202/{collection_name}/_stats").json() +print(response["_all"]["total"]["store"]["size_in_bytes"]) diff --git a/assets/dynamically-structured/elasticsearch/measure-compressed-size.sh b/assets/dynamically-structured/elasticsearch/measure-compressed-size.sh new file mode 100755 index 0000000..fae40ff --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/measure-compressed-size.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/measure-compressed-size.py" diff --git a/assets/dynamically-structured/elasticsearch/measure-decompressed-size.sh b/assets/dynamically-structured/elasticsearch/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/elasticsearch/methodology.md b/assets/dynamically-structured/elasticsearch/methodology.md new file mode 100644 index 0000000..208f9ab --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/methodology.md @@ -0,0 +1,24 @@ +# Elasticsearch methodology + +## Basics + +Version: [8.6.2][download] + +## Setup + +We deploy [elasticsearch] in a single-node configuration. + +## Specifics + +We disable the security feature of [xpack][disabling-xpack]. We use Elasticsearch's Python package for data ingestion +and search operations. + +Some preprocessing is necessary to make the dataset searchable in Elasticsearch. For more details, +refer to the `traverse_data` function in `ingest_script` . This process generally involves +reorganizing specific fields, moving them into outer or inner objects to ensure proper query +functionality. + + +[download]: https://www.elastic.co/downloads/past-releases/elasticsearch-8-6-2 +[disabling-xpack]: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html +[elasticsearch]: https://www.elastic.co/downloads/elasticsearch diff --git a/assets/dynamically-structured/elasticsearch/reset.py b/assets/dynamically-structured/elasticsearch/reset.py new file mode 100644 index 0000000..8d9edfa --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/reset.py @@ -0,0 +1,5 @@ +import requests + +collection_name = "elasticsearch_clp_bench" + +requests.delete(f"http://localhost:9202/{collection_name}") diff --git a/assets/dynamically-structured/elasticsearch/reset.sh b/assets/dynamically-structured/elasticsearch/reset.sh new file mode 100755 index 0000000..47c7380 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/reset.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/reset.py" diff --git a/assets/dynamically-structured/elasticsearch/results.json b/assets/dynamically-structured/elasticsearch/results.json new file mode 100644 index 0000000..fcdb832 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/results.json @@ -0,0 +1,36 @@ +{ + "target": "elasticsearch", + "targetDisplayedName": "Elasticsearch", + "displayedOrder": 2, + "isEnable": true, + "type": 2, + "ingestTime": 19657020, + "compressedSize": 19141618565, + "avgIngestMem": 10376729068, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 5961930752, + "queryTimes": [ + 2280, + 9440, + 490, + 590, + 2110, + 1700 + ] + }, + { + "metric": 2, + "avgQueryMem": 6565423104, + "queryTimes": [ + 3050, + 9270, + 520, + 560, + 6000, + 1660 + ] + } + ] +} diff --git a/assets/dynamically-structured/elasticsearch/search.py b/assets/dynamically-structured/elasticsearch/search.py new file mode 100644 index 0000000..7780fa0 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/search.py @@ -0,0 +1,47 @@ +import logging +import sys + +from elasticsearch import Elasticsearch + +collection_name = "elasticsearch_clp_bench" + +es = Elasticsearch( + "http://localhost:9202", request_timeout=30, max_retries=10, retry_on_timeout=True +) +results = [] + +query = sys.argv[1] + + +# Function to execute a query without cache +def execute_query_without_cache(query): + # Initialize the scroll + page = es.search( + index=collection_name, + scroll="8m", # Keep the search context open for 2 minutes + body=query, + request_cache=False, + ) + + for result in page["hits"]["hits"]: + results.append(result) + + # Start scrolling + sid = page["_scroll_id"] + while True: + page = es.scroll(scroll_id=sid, scroll="8m") + if not page["hits"]["hits"]: + break + for result in page["hits"]["hits"]: + results.append(result) + + +# Execute the query +while True: + try: + execute_query_without_cache(query) + print(len(results)) + break + except Exception as e: + logging.error(e) + continue diff --git a/assets/dynamically-structured/elasticsearch/search.sh b/assets/dynamically-structured/elasticsearch/search.sh new file mode 100755 index 0000000..d8243ed --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/search.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/search.py" "$1" diff --git a/assets/dynamically-structured/elasticsearch/terminate.sh b/assets/dynamically-structured/elasticsearch/terminate.sh new file mode 100755 index 0000000..6f84db0 --- /dev/null +++ b/assets/dynamically-structured/elasticsearch/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +pkill -f java diff --git a/assets/dynamically-structured/mongodb/Dockerfile b/assets/dynamically-structured/mongodb/Dockerfile new file mode 100644 index 0000000..bc74bc7 --- /dev/null +++ b/assets/dynamically-structured/mongodb/Dockerfile @@ -0,0 +1 @@ +FROM mongodb/mongodb-enterprise-server:latest diff --git a/assets/dynamically-structured/mongodb/clear-cache.sh b/assets/dynamically-structured/mongodb/clear-cache.sh new file mode 100755 index 0000000..78387c4 --- /dev/null +++ b/assets/dynamically-structured/mongodb/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/dynamically-structured/mongodb/config.yaml b/assets/dynamically-structured/mongodb/config.yaml new file mode 100644 index 0000000..fbc3033 --- /dev/null +++ b/assets/dynamically-structured/mongodb/config.yaml @@ -0,0 +1,25 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: mongodb-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets/mongod.log +hot_run_warm_up_times: 3 +related_processes: + - mongod + - mongosh + - mongoimport + - mongoexport +queries: + - '''{ "attr.tickets": { "$exists": true}}''' + - '''{"id": {"$eq": 22419}}''' + - > + '{"attr.message.msg": {"$regex": "^log_release"}, "attr.message.session_name": "connection"}' + - > + '{"ctx": "initandlisten", "$or": [{"msg": {"$exists": true, "$ne": "WiredTiger message"}}, { + "attr.message.msg": {"$regex": "^log_remove"}}]}' + - '''{"c": "WTWRTLOG", "attr.message.ts_sec": {"$gt": 1679490000}}''' + - '''{"ctx": "FlowControlRefresher", "attr.numTrimmed": {"$eq": 0}}''' diff --git a/assets/dynamically-structured/mongodb/container-name b/assets/dynamically-structured/mongodb/container-name new file mode 100644 index 0000000..91922d9 --- /dev/null +++ b/assets/dynamically-structured/mongodb/container-name @@ -0,0 +1 @@ +mongodb-clp-bench \ No newline at end of file diff --git a/assets/dynamically-structured/mongodb/docker-build.sh b/assets/dynamically-structured/mongodb/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/dynamically-structured/mongodb/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/dynamically-structured/mongodb/docker-run.sh b/assets/dynamically-structured/mongodb/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/dynamically-structured/mongodb/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/dynamically-structured/mongodb/ingest.sh b/assets/dynamically-structured/mongodb/ingest.sh new file mode 100755 index 0000000..335946a --- /dev/null +++ b/assets/dynamically-structured/mongodb/ingest.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +collection_name=mongodb_clp_bench + +mongoimport \ + --numInsertionWorkers=1 \ + --db logs \ + --collection "$collection_name" \ + --file "$1" \ + >/dev/null 2>&1 diff --git a/assets/dynamically-structured/mongodb/launch.sh b/assets/dynamically-structured/mongodb/launch.sh new file mode 100755 index 0000000..721796e --- /dev/null +++ b/assets/dynamically-structured/mongodb/launch.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Check if MongoDB is installed +if ! command -v mongod &>/dev/null; then + exit 1 +fi + +# Create the data directory if it doesn't exist +mkdir -p /data/db + +# Start MongoDB with specified options +mongod \ + --fork \ + --syslog \ + --wiredTigerCollectionBlockCompressor zstd \ + --zstdDefaultCompressionLevel 3 \ + >/dev/null 2>&1 + +# Wait for 2 seconds to ensure MongoDB starts +sleep 2 diff --git a/assets/dynamically-structured/mongodb/measure-compressed-size.sh b/assets/dynamically-structured/mongodb/measure-compressed-size.sh new file mode 100755 index 0000000..8f81f48 --- /dev/null +++ b/assets/dynamically-structured/mongodb/measure-compressed-size.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +collection_name=mongodb_clp_bench +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" + +bash "${script_dir}/terminate.sh" +bash "${script_dir}/launch.sh" +mongosh logs --eval "'db.${collection_name}.storageSize().toString()'" diff --git a/assets/dynamically-structured/mongodb/measure-decompressed-size.sh b/assets/dynamically-structured/mongodb/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/dynamically-structured/mongodb/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/mongodb/methodology.md b/assets/dynamically-structured/mongodb/methodology.md new file mode 100644 index 0000000..50fa4c8 --- /dev/null +++ b/assets/dynamically-structured/mongodb/methodology.md @@ -0,0 +1,20 @@ +# MongoDB methodology + +## Basics + +Version: [6.0.19][download] + +## Setup + +We use `mongosh`, `mongod`, `mongoexport`, and `mongoimport` to ingest the dataset and perform query benchmarking. No +special preprocessing is required for the dataset. + +## Specifics + +We use the [ZStandard][zstandard] compressor with the [default compression level][compression_level] +set to 3. + + +[compression_level]: https://source.wiredtiger.com/3.1.0/compression.html#:~:text=%22extensions%3D%5B%2Fusr%2Flocal%2Flib%2Flibwiredtiger_zstd.sothe%20additional%20configuration%20argument%20compression_level%20. +[download]: https://hub.docker.com/layers/mongodb/mongodb-enterprise-server/6.0.19-ubuntu2204/images/sha256-2cb24cf730152d9b0710841a9ae6a50732c48fa6c7111bf7b4a84a5a8df5bc01?context=explore +[zstandard]: https://www.mongodb.com/docs/manual/reference/program/mongod/#std-option-mongod.--wiredTigerCollectionBlockCompressor diff --git a/assets/dynamically-structured/mongodb/reset.sh b/assets/dynamically-structured/mongodb/reset.sh new file mode 100755 index 0000000..bf70396 --- /dev/null +++ b/assets/dynamically-structured/mongodb/reset.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +collection_name=mongodb_clp_bench + +mongosh logs --eval "db.${collection_name}.drop()" >/dev/null 2>&1 diff --git a/assets/dynamically-structured/mongodb/results.json b/assets/dynamically-structured/mongodb/results.json new file mode 100644 index 0000000..47f14db --- /dev/null +++ b/assets/dynamically-structured/mongodb/results.json @@ -0,0 +1,36 @@ +{ + "target": "mongodb", + "targetDisplayedName": "MongoDB", + "displayedOrder": 4, + "isEnable": true, + "type": 2, + "ingestTime": 14067665, + "compressedSize": 4697899008, + "avgIngestMem": 2394053632, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 49888236544, + "queryTimes": [ + 140598, + 140027, + 140363, + 195352, + 128771, + 142634 + ] + }, + { + "metric": 2, + "avgQueryMem": 46862787584, + "queryTimes": [ + 184740, + 136416, + 146571, + 203285, + 137266, + 151620 + ] + } + ] +} diff --git a/assets/dynamically-structured/mongodb/search.sh b/assets/dynamically-structured/mongodb/search.sh new file mode 100755 index 0000000..3f19202 --- /dev/null +++ b/assets/dynamically-structured/mongodb/search.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +collection_name=mongodb_clp_bench + +mongoexport --quiet --db logs --collection "$collection_name" --query "$1" | wc -l diff --git a/assets/dynamically-structured/mongodb/terminate.sh b/assets/dynamically-structured/mongodb/terminate.sh new file mode 100755 index 0000000..4434c94 --- /dev/null +++ b/assets/dynamically-structured/mongodb/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +mongod --shutdown >/dev/null 2>&1 diff --git a/assets/dynamically-structured/template/Dockerfile b/assets/dynamically-structured/template/Dockerfile new file mode 100644 index 0000000..ee1e4eb --- /dev/null +++ b/assets/dynamically-structured/template/Dockerfile @@ -0,0 +1,11 @@ +# This file is used for building the container, ensuring installation of the required tool and +# dependencies + +# If there is any dedicated image available, you should build the benchmarking image on top of that +FROM ghcr.io/y-scope/clp/clp-core-dependencies-x86-ubuntu-jammy:main + +# Install necessary packages +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + tmux \ + vim diff --git a/assets/dynamically-structured/template/clear-cache.sh b/assets/dynamically-structured/template/clear-cache.sh new file mode 100755 index 0000000..b10a69d --- /dev/null +++ b/assets/dynamically-structured/template/clear-cache.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# This script clears the tool's cache, essential for cold runs +# +# If there is any internal cache mechanism of the application, you should also clear them here +# (or turn it off when launching it) + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/dynamically-structured/template/config.yaml b/assets/dynamically-structured/template/config.yaml new file mode 100644 index 0000000..d54cedd --- /dev/null +++ b/assets/dynamically-structured/template/config.yaml @@ -0,0 +1,45 @@ +# This config file contains essential benchmarking configurations + +system_metric: + # Toggle to enable system metric monitoring (e.g., memory usage). Set to `true` to activate + enable: true + memory: + # Time interval (in seconds) for polling memory during data ingestion + ingest_polling_interval: 5 + # Time interval (in seconds) for polling memory during query benchmarking + run_query_benchmark_polling_interval: 5 + +# Identifier for the benchmark container. Usually `${tool}-clp-bench` +container_id: {Your Tool Name}-clp-bench +# Path to the assets directory in the container. Leave as default unless modifying `docker-run.sh` +assets_path: /home/assets +# Path for datasets in the container; may refer to a file, directory, or file pattern. clp-bench +# does not validate the dataset's presence +datasets_path: /home/datasets/mongod.log +# Number of repetitions for query warm-up in hot-run mode before measuring latency. This may be +# automated in the future +hot_run_warm_up_times: 3 +# List of command substrings (the first substring of the entire command split by space from `ps +# aux`) to track relevant memory usage +related_processes: + - {Related Process Headers} +# Array of queries for benchmarking. Ensure escape characters are carefully handled. Note that each +# query should be wrapped with double quotes (or single quote, depends on whether the query contains +# double quotes) as the query will first be passed as an argument of `docker exec` rather than +# directly to the scripts +queries: + # Query all JSON log lines containing "attr" object with "tickets" field + - '"attr.tickets:*"' + # Query all JSON log lines containing "id" field that equals to 22419 + - '"id: 22419"' + # Query all JSON log lines containing "attr" object with a nested "message" object with a "msg" + # field that starts with "log_release" and a "session_name" field that is "connection" + - '"attr.message.msg: log_release* AND attr.message.session_name: connection"' + # Query all JSON log lines where "ctx" is "initandlisten" and either "msg" does not equal to + # "WiredTigermessage" or "attr.message.msg" starts with "log_remove" + - '''ctx: initandlisten AND (NOT msg: "WiredTigermessage" OR attr.message.msg: log_remove*)''' + # Query all JSON log lines where "c" is "WTWRTLOG" and "attr.message.ts_sec" is greater than + # 1679490000 + - '"c: WTWRTLOG AND attr.message.ts_sec > 1679490000"' + # Query all JSON log lines where "ctx" is "FlowControlRefresher" and "attr.numTrimmed" equals 0 + - '"ctx: FlowControlRefresher AND attr.numTrimmed: 0"' diff --git a/assets/dynamically-structured/template/container-name b/assets/dynamically-structured/template/container-name new file mode 100644 index 0000000..d0d9686 --- /dev/null +++ b/assets/dynamically-structured/template/container-name @@ -0,0 +1 @@ +{Your Tool Name}-clp-bench \ No newline at end of file diff --git a/assets/dynamically-structured/template/docker-build.sh b/assets/dynamically-structured/template/docker-build.sh new file mode 100755 index 0000000..613b170 --- /dev/null +++ b/assets/dynamically-structured/template/docker-build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script builds the container as per the `Dockerfile` in the same directory. Usually, only the +# `container_name` variable should be adjusted to match the `container_id` in `config.yaml` + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/dynamically-structured/template/docker-run.sh b/assets/dynamically-structured/template/docker-run.sh new file mode 100755 index 0000000..ada9e03 --- /dev/null +++ b/assets/dynamically-structured/template/docker-run.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# This script runs the container, taking the dataset path as an argument. Typically, only the +# `container_name` variable needs alignment with `container_id` in `config.yaml` + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/dynamically-structured/template/ingest.sh b/assets/dynamically-structured/template/ingest.sh new file mode 100755 index 0000000..6c40a90 --- /dev/null +++ b/assets/dynamically-structured/template/ingest.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script handles data ingestion, with clp-bench measuring the total latency of this script. +# Avoid adding extra operations + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +# Your ingest command goes here diff --git a/assets/dynamically-structured/template/launch.sh b/assets/dynamically-structured/template/launch.sh new file mode 100755 index 0000000..de03e76 --- /dev/null +++ b/assets/dynamically-structured/template/launch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This script initializes and starts the tool (e.g., if it functions as a server or service) + +# Your launch command goes here diff --git a/assets/dynamically-structured/template/measure-compressed-size.sh b/assets/dynamically-structured/template/measure-compressed-size.sh new file mode 100755 index 0000000..8d1ef83 --- /dev/null +++ b/assets/dynamically-structured/template/measure-compressed-size.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This script measures the compressed data size post-ingestion, usually via tool-specific methods + +# Your archive measuring command goes here diff --git a/assets/dynamically-structured/template/measure-decompressed-size.sh b/assets/dynamically-structured/template/measure-decompressed-size.sh new file mode 100755 index 0000000..9198ff6 --- /dev/null +++ b/assets/dynamically-structured/template/measure-decompressed-size.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script measures the raw dataset size before ingestion. Typically unchanged, it takes +# `datasets_path` from `config.yaml` and uses `du -bc` for size calculation in bytes + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/dynamically-structured/template/methodology.md b/assets/dynamically-structured/template/methodology.md new file mode 100644 index 0000000..367229f --- /dev/null +++ b/assets/dynamically-structured/template/methodology.md @@ -0,0 +1,16 @@ +# Tool name methodology + +## Basics + +Version: [1.0.0][download] + +## Setup + +Describe any manual set up steps necessary for the tool. + +## Specifics + +Describe any specific tuning, preprocessing or configuration that beyond the defaults you made for +benchmarking. If there is no specifics you can delete this section. + +[download]: https://via.placeholder.com/20 diff --git a/assets/dynamically-structured/template/reset.sh b/assets/dynamically-structured/template/reset.sh new file mode 100755 index 0000000..132e4ea --- /dev/null +++ b/assets/dynamically-structured/template/reset.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# This file prepares a clean environment by removing previous data (e.g., dropping tables); runs +# after `launch.sh` in `ingest` mode diff --git a/assets/dynamically-structured/template/results.json b/assets/dynamically-structured/template/results.json new file mode 100644 index 0000000..1139ec5 --- /dev/null +++ b/assets/dynamically-structured/template/results.json @@ -0,0 +1,36 @@ +{ + "target": "yourTool", + "targetDisplayedName": "Your Tool Name", + "displayedOrder": 1, + "isEnable": true, + "type": 2, + "ingestTime": 999, + "compressedSize": 999, + "avgIngestMem": 999, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 999, + "queryTimes": [ + 999, + 999, + 999, + 999, + 999, + 999 + ] + }, + { + "metric": 2, + "avgQueryMem": 999, + "queryTimes": [ + 999, + 999, + 999, + 999, + 999, + 999 + ] + } + ] +} diff --git a/assets/dynamically-structured/template/search.sh b/assets/dynamically-structured/template/search.sh new file mode 100755 index 0000000..40dbe4e --- /dev/null +++ b/assets/dynamically-structured/template/search.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# This script takes queries specified in `config.yaml` as command line argument and executes them + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +# Your query command goes here diff --git a/assets/dynamically-structured/template/terminate.sh b/assets/dynamically-structured/template/terminate.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/dynamically-structured/template/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/elasticsearch-unstructured/compress.py b/assets/elasticsearch-unstructured/compress.py deleted file mode 100644 index 15a97d1..0000000 --- a/assets/elasticsearch-unstructured/compress.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/python3 -import json -import logging -import os -import pathlib -import requests -import time -from elasticsearch import Elasticsearch, helpers -from elasticsearch.helpers import parallel_bulk, streaming_bulk -import sys -import glob - -logging.basicConfig(format='%(asctime)s [%(pathname)s:%(lineno)d] - %(message)s', datefmt='%y-%b-%d %H:%M:%S', level=logging.INFO) -es_logger = logging.getLogger('elasticsearch') -es_logger.setLevel(logging.WARNING) -es_transport_logger = logging.getLogger('elastic_transport.transport') -es_transport_logger.setLevel(logging.WARNING) - -original_sizes = [] -compressed_sizes = [] -compression_ratios = [] -ingestion_times = [] -ingestion_speeds = [] - -# path_pattern = '/home/datasets/worker*/worker*/*log*' -path_pattern = sys.argv[1] -# Find all files matching the pattern -log_files = glob.glob(path_pattern) -logging.info(f'Total log files: {len(log_files)}') - -def get_compressed_size(dataset): - response = requests.get(f'http://localhost:9201/{dataset.replace("-", "_")}/_stats').json() - return response['_all']['total']['store']['size_in_bytes'] - - -def traverse_data(index_name): - for log_file in log_files: - logging.info(f'Processed file: {log_file}') - with open(log_file, 'r') as f: - for line in f.readlines(): - yield { - '_index': index_name, - '_source': { - 'log_line': line - } - } - - -def ingest_dataset(): - es = Elasticsearch('http://localhost:9201', request_timeout=1200, retry_on_timeout=True) - dataset='hadoop' - index_name = dataset - - requests.delete(f"http://localhost:9201/{index_name}") - logging.info(f'Begin ingesting {dataset}') - start_time = time.time() - count = 0 - for success, info in streaming_bulk(es, traverse_data(dataset), raise_on_error=False, raise_on_exception=False, chunk_size=100000, request_timeout=3600): - if success: - count += 1 - else: - logging.error(f"Failed to index document at {count}: {info}") - if count % 1000000 == 0: - logging.info(f'Index {count} logs') - - requests.post(f"http://localhost:9201/{index_name}/_flush/") - logging.debug(f'Flush all data in {index_name}') - end_time = time.time() - logging.info(f'Finish ingesting {dataset}') - - file_size = 0 - for log_file in log_files: - file_size += os.path.getsize(log_file) - time.sleep(5) - compressed_size = get_compressed_size(dataset) - compression_ratio = file_size / compressed_size - ingestion_time = end_time - start_time - ingestion_speed = file_size / ingestion_time / 1024 / 1024 - - - logging.info(f'Original size for {dataset} is {file_size}') - logging.info(f'Compressed size for {dataset} is {compressed_size}') - logging.info(f'Compression ratio for {dataset} is {compression_ratio}') - logging.info(f'Ingestion time for {dataset} is {ingestion_time} s') - logging.info(f'Ingestion speed for {dataset} is {ingestion_speed} MB/s') - - -if __name__ == "__main__": - try: - ingest_dataset() - except Exception as e: - print(e) - diff --git a/assets/elasticsearch-unstructured/docker_build.sh b/assets/elasticsearch-unstructured/docker_build.sh deleted file mode 100644 index 8a80cdb..0000000 --- a/assets/elasticsearch-unstructured/docker_build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -u - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -container_name="elasticsearch-xiaochong" - -docker build \ - -t "$container_name" \ - "$script_dir" \ - --file "$script_dir"/Dockerfile diff --git a/assets/elasticsearch-unstructured/docker_run.sh b/assets/elasticsearch-unstructured/docker_run.sh deleted file mode 100644 index 5661031..0000000 --- a/assets/elasticsearch-unstructured/docker_run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e -set -u - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -container_name="elasticsearch-xiaochong" - -docker run \ - --privileged \ - -it \ - --rm \ - -m 512m \ - --workdir /home \ - --network host \ - --name "$container_name" \ - --mount "type=bind,src=$script_dir,dst=/home/assets" \ - --mount "type=bind,src=$1,dst=/home/datasets" \ - --mount "type=bind,src=$2,dst=/var/lib/elasticsearch" \ - "$container_name" \ - /bin/bash -l diff --git a/assets/elasticsearch-unstructured/ela-config.yaml b/assets/elasticsearch-unstructured/ela-config.yaml deleted file mode 100644 index 3bf9762..0000000 --- a/assets/elasticsearch-unstructured/ela-config.yaml +++ /dev/null @@ -1,30 +0,0 @@ -system_metric: - enable: True - memory: - ingest_polling_interval: 10 - run_query_benchmark_polling_interval: 5 - -elasticsearch: - container_id: elasticsearch-xiaochong - launch_script_path: /home/assets/start-ela.sh - compress_script_path: /home/assets/compress.py - search_script_path: /home/assets/query.py - terminate_script_path: /home/assets/stop-ela.sh - memory_polling_script_path: /home/assets/poll_mem.py - data_path: /var/lib/elasticsearch - log_path: /var/log/elasticsearch - dataset_path: /home/datasets/worker*/worker*/*log* - queries: - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: Container "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " to pid 21177 as user "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 10000 reply: "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 10 reply: "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 178.2 MB "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 1.9 GB "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "job_1528179349176_24837"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "blk_1075089282_1348458"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "hdfs://master:8200/HiBench/Bayes/temp/worddict"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " abcde "}}}}, "size": 10000}' \ No newline at end of file diff --git a/assets/elasticsearch-unstructured/poll_mem.py b/assets/elasticsearch-unstructured/poll_mem.py deleted file mode 100644 index b18c595..0000000 --- a/assets/elasticsearch-unstructured/poll_mem.py +++ /dev/null @@ -1,11 +0,0 @@ -from elasticsearch import Elasticsearch - -try: - es = Elasticsearch("http://localhost:9201") - stats = es.nodes.stats(metric=['jvm', 'process']) - node_id = list(stats['nodes'].keys())[0] - node_stats = stats['nodes'][node_id] - - print(node_stats['jvm']['mem']['heap_used_in_bytes']) -except Exception: - print(-1) \ No newline at end of file diff --git a/assets/elasticsearch-unstructured/query.py b/assets/elasticsearch-unstructured/query.py deleted file mode 100644 index 2c24c4c..0000000 --- a/assets/elasticsearch-unstructured/query.py +++ /dev/null @@ -1,45 +0,0 @@ -from elasticsearch import Elasticsearch, helpers -from elasticsearch.helpers import parallel_bulk, streaming_bulk -from threading import Thread, Event -import logging -import sys - -logging.basicConfig(format='%(asctime)s [%(pathname)s:%(lineno)d] - %(message)s', datefmt='%y-%b-%d %H:%M:%S', level=logging.INFO) -es_logger = logging.getLogger('elasticsearch') -es_logger.setLevel(logging.WARNING) -es_transport_logger = logging.getLogger('elastic_transport.transport') -es_transport_logger.setLevel(logging.WARNING) - - -es = Elasticsearch("http://localhost:9201", timeout=30, max_retries=10, retry_on_timeout=True) - -# '{"query": {"bool": {"must": {"match_phrase": {"log_line": " org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "}}}}, "size": 10000}' -query = sys.argv[1] - -# Function to execute a query without cache -def execute_query_without_cache(query): - # Initialize the scroll - page = es.search( - index="hadoop", - scroll="8m", # Keep the search context open for 2 minutes - body=query, - request_cache=False - ) - - for result in page['hits']['hits']: - print(result) - - # Start scrolling - sid = page['_scroll_id'] - while True: - page = es.scroll(scroll_id=sid, scroll='8m') - if not page['hits']['hits']: - break - for result in page['hits']['hits']: - print(result) - - es.indices.clear_cache(index="hadoop") - -# Execute the query -execute_query_without_cache(query) - diff --git a/assets/elasticsearch-unstructured/start-ela.sh b/assets/elasticsearch-unstructured/start-ela.sh deleted file mode 100644 index 52cb8b1..0000000 --- a/assets/elasticsearch-unstructured/start-ela.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -chown -R elasticsearch:elasticsearch /var/lib/elasticsearch - -sed -i 's/#http.port: 9200/http.port: 9201/' /etc/elasticsearch/elasticsearch.yml -sed -i 's/xpack.security.enabled: true/xpack.security.enabled: false/' /etc/elasticsearch/elasticsearch.yml -sed -i '/cluster.initial_master_nodes/d' /etc/elasticsearch/elasticsearch.yml -grep -q 'discovery.type: single-node' /etc/elasticsearch/elasticsearch.yml || echo 'discovery.type: single-node' >> /etc/elasticsearch/elasticsearch.yml - -sed -i '/elasticsearch/ s/\/bin\/false/\/bin\/bash/' /etc/passwd -sed -i '/elasticsearch/ s/\/nonexistent/\/usr\/share\/elasticsearch/' /etc/passwd - -su elasticsearch -c '/usr/share/elasticsearch/bin/elasticsearch -d' \ No newline at end of file diff --git a/assets/elasticsearch-unstructured/stop-ela.sh b/assets/elasticsearch-unstructured/stop-ela.sh deleted file mode 100644 index ed333e2..0000000 --- a/assets/elasticsearch-unstructured/stop-ela.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -pkill -f java \ No newline at end of file diff --git a/assets/elasticsearch/compress.py b/assets/elasticsearch/compress.py deleted file mode 100644 index 3e29ed2..0000000 --- a/assets/elasticsearch/compress.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -import json -import logging -import os -import pathlib -import requests -import time -from elasticsearch import Elasticsearch, helpers -from elasticsearch.helpers import parallel_bulk, streaming_bulk -import sys - -logging.basicConfig(format='%(asctime)s [%(pathname)s:%(lineno)d] - %(message)s', datefmt='%y-%b-%d %H:%M:%S', level=logging.INFO) -es_logger = logging.getLogger('elasticsearch') -es_logger.setLevel(logging.WARNING) -es_transport_logger = logging.getLogger('elastic_transport.transport') -es_transport_logger.setLevel(logging.WARNING) - -original_sizes = [] -compressed_sizes = [] -compression_ratios = [] -ingestion_times = [] -ingestion_speeds = [] - -log_path = sys.argv[1] -# log_path = '/home/muslope/mongodb-test/mongod.log.2023-03-22T03-45-46' -# log_path = '/home/muslope/datasets/mongod.log' - -def get_compressed_size(dataset): - response = requests.get(f'http://localhost:9202/{dataset.replace("-", "_")}/_stats').json() - return response['_all']['total']['store']['size_in_bytes'] - - -def traverse_data(index_name): - with open(log_path, encoding='utf-8') as f: - for line in f: - json_line = json.loads(line) - if 'attr' in json_line: - attr = json_line['attr'] - if 'uuid' in attr and isinstance(attr['uuid'], dict): - uuid = attr['uuid']['uuid']['$uuid'] - json_line['attr']['uuid'] = uuid - if 'error' in attr and isinstance(attr['error'], str): - error_msg = attr['error'] - json_line['attr']['error'] = {} - json_line['attr']['error']['errmsg'] = error_msg - if 'command' in attr: - command = attr['command'] - if isinstance(command, str): - json_line['attr']['command'] = {} - json_line['attr']['command']['command'] = command - if isinstance(command, dict) and \ - 'q' in command and isinstance(command['q'], dict) and \ - '_id' in command['q'] and not isinstance(command['q']['_id'], dict): - id_value = str(command['q']['_id']) - json_line['attr']['command']['q']['_id'] = {} - json_line['attr']['command']['q']['_id']['_ooid'] = id_value - if 'writeConcern' in attr and isinstance(attr['writeConcern'], dict) and \ - 'w' in attr['writeConcern'] and isinstance(attr['writeConcern']['w'], int): - w = attr['writeConcern']['w'] - json_line['attr']['writeConcern']['w'] = str(w) - if 'query' in attr and isinstance(attr['query'], dict) and \ - '_id' in attr['query'] and not isinstance(attr['query']['_id'], dict): - id_value = str(attr['query']['_id']) - json_line['attr']['query']['_id'] = {} - json_line['attr']['query']['_id']['_ooid'] = id_value - yield { - '_index': index_name, - '_source': json_line, - } - - -def ingest_dataset(): - es = Elasticsearch('http://localhost:9202', request_timeout=1200, retry_on_timeout=True) - dataset='mongodb_new_single_1' - index_name = 'mongodb_new_single_1' - - requests.delete(f"http://localhost:9202/{index_name}") - logging.info(f'Begin ingesting {dataset}') - start_time = time.time() - count = 0 - for success, info in streaming_bulk(es, traverse_data(dataset), raise_on_error=False, raise_on_exception=False, chunk_size=10000, request_timeout=120): - if success: - count += 1 - else: - logging.error(f"Failed to index document at {count}: {info}") - if count % 100000 == 0: - logging.info(f'Index {count} logs') - - requests.post(f"http://localhost:9202/{index_name}/_flush/") - logging.debug(f'Flush all data in {index_name}') - end_time = time.time() - logging.info(f'Finish ingesting {dataset}') - - file_size = os.path.getsize(log_path) - time.sleep(5) - compressed_size = get_compressed_size(dataset) - compression_ratio = file_size / compressed_size - ingestion_time = end_time - start_time - ingestion_speed = file_size / ingestion_time / 1024 / 1024 - - - logging.info(f'Original size for {dataset} is {file_size}') - logging.info(f'Compressed size for {dataset} is {compressed_size}') - logging.info(f'Compression ratio for {dataset} is {compression_ratio}') - logging.info(f'Ingestion time for {dataset} is {ingestion_time} s') - logging.info(f'Ingestion speed for {dataset} is {ingestion_speed} MB/s') - - -if __name__ == "__main__": - try: - ingest_dataset() - except Exception as e: - print(e) - \ No newline at end of file diff --git a/assets/elasticsearch/docker_build.sh b/assets/elasticsearch/docker_build.sh deleted file mode 100644 index 260bf82..0000000 --- a/assets/elasticsearch/docker_build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -u - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -container_name="elasticsearch-semi-xiaochong" - -docker build \ - -t "$container_name" \ - "$script_dir" \ - --file "$script_dir"/Dockerfile diff --git a/assets/elasticsearch/docker_run.sh b/assets/elasticsearch/docker_run.sh deleted file mode 100644 index 5cb7fa1..0000000 --- a/assets/elasticsearch/docker_run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e -set -u - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -container_name="elasticsearch-semi-xiaochong" - -docker run \ - --privileged \ - --rm \ - -it \ - -m 512m \ - --workdir /home \ - --network host \ - --name "$container_name" \ - --mount "type=bind,src=$script_dir,dst=/home/assets" \ - --mount "type=bind,src=$1,dst=/home/datasets" \ - --mount "type=bind,src=$2,dst=/var/lib/elasticsearch" \ - "$container_name" \ - /bin/bash -l \ No newline at end of file diff --git a/assets/elasticsearch/ela-config.yaml b/assets/elasticsearch/ela-config.yaml deleted file mode 100644 index deb3b01..0000000 --- a/assets/elasticsearch/ela-config.yaml +++ /dev/null @@ -1,23 +0,0 @@ -system_metric: - enable: True - memory: - ingest_polling_interval: 10 - run_query_benchmark_polling_interval: 10 - -elasticsearch: - container_id: elasticsearch-semi-xiaochong - launch_script_path: /home/assets/start-ela.sh - compress_script_path: /home/assets/compress.py - search_script_path: /home/assets/query.py - terminate_script_path: /home/assets/stop-ela.sh - memory_polling_script_path: /home/assets/poll_mem.py - data_path: /var/lib/elasticsearch - log_path: /var/log/elasticsearch - dataset_path: /home/datasets/mongod.log - queries: - - '{"query": {"exists": {"field": "attr.tickets"}}, "size": 10000}' - - '{"query": {"term": {"id": 22419}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"wildcard": {"attr.message.msg": "log_release*"}}, {"match": {"attr.message.session_name": "connection"}}]}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"ctx": "initandlisten"}}], "should": [{"wildcard": {"attr.message.msg": "log_remove*"}}, {"bool": {"must_not": [{"match_phrase": {"msg": "WiredTiger message"}}]}}], "minimum_should_match": 1}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"c": "WTWRTLOG"}}, {"range": {"attr.message.ts_sec": {"gt": 1679490000}}}]}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"ctx": "FlowControlRefresher"}}, {"match": {"attr.numTrimmed": 0}}]}}, "size": 10000}' \ No newline at end of file diff --git a/assets/elasticsearch/poll_mem.py b/assets/elasticsearch/poll_mem.py deleted file mode 100644 index bbfe9c2..0000000 --- a/assets/elasticsearch/poll_mem.py +++ /dev/null @@ -1,11 +0,0 @@ -from elasticsearch import Elasticsearch - -try: - es = Elasticsearch("http://localhost:9202") - stats = es.nodes.stats(metric=['jvm', 'process']) - node_id = list(stats['nodes'].keys())[0] - node_stats = stats['nodes'][node_id] - - print(node_stats['jvm']['mem']['heap_used_in_bytes']) -except Exception: - print(-1) \ No newline at end of file diff --git a/assets/elasticsearch/query.py b/assets/elasticsearch/query.py deleted file mode 100644 index 5469909..0000000 --- a/assets/elasticsearch/query.py +++ /dev/null @@ -1,50 +0,0 @@ -from elasticsearch import Elasticsearch, helpers -from elasticsearch.helpers import parallel_bulk, streaming_bulk -from threading import Thread, Event -import logging -import sys - -logging.basicConfig(format='%(asctime)s [%(pathname)s:%(lineno)d] - %(message)s', datefmt='%y-%b-%d %H:%M:%S', level=logging.INFO) -es_logger = logging.getLogger('elasticsearch') -es_logger.setLevel(logging.WARNING) -es_transport_logger = logging.getLogger('elastic_transport.transport') -es_transport_logger.setLevel(logging.WARNING) - - -es = Elasticsearch("http://localhost:9202", timeout=30, max_retries=10, retry_on_timeout=True) - -query = sys.argv[1] - -# Function to execute a query without cache -def execute_query_without_cache(query): - # Initialize the scroll - page = es.search( - index="mongodb_new_single_1", - scroll="8m", # Keep the search context open for 2 minutes - body=query, - request_cache=False - ) - - for result in page['hits']['hits']: - print(result) - - # Start scrolling - sid = page['_scroll_id'] - while True: - page = es.scroll(scroll_id=sid, scroll='8m') - if not page['hits']['hits']: - break - for result in page['hits']['hits']: - print(result) - - es.indices.clear_cache(index="mongodb_new_single_1") - -# Execute the query -while (True): - try: - execute_query_without_cache(query) - break - except Exception as e: - logging.error(e) - continue - diff --git a/assets/elasticsearch/start-ela.sh b/assets/elasticsearch/start-ela.sh deleted file mode 100644 index 172c034..0000000 --- a/assets/elasticsearch/start-ela.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -chown -R elasticsearch:elasticsearch /var/lib/elasticsearch - -sed -i 's/#http.port: 9200/http.port: 9202/' /etc/elasticsearch/elasticsearch.yml -sed -i 's/xpack.security.enabled: true/xpack.security.enabled: false/' /etc/elasticsearch/elasticsearch.yml -sed -i '/cluster.initial_master_nodes/d' /etc/elasticsearch/elasticsearch.yml -grep -q 'discovery.type: single-node' /etc/elasticsearch/elasticsearch.yml || echo 'discovery.type: single-node' >> /etc/elasticsearch/elasticsearch.yml - -sed -i '/elasticsearch/ s/\/bin\/false/\/bin\/bash/' /etc/passwd -sed -i '/elasticsearch/ s/\/nonexistent/\/usr\/share\/elasticsearch/' /etc/passwd - -# It seems the environment variables sometimes does not work, need stop and start the elasticsearch in the container manually to ensure it is applied -# use to check: curl -XGET 'http://localhost:9202/_nodes/stats/jvm?pretty' -# ES_JAVA_OPTS="-Xms256m -Xmx256m" su elasticsearch -c '/usr/share/elasticsearch/bin/elasticsearch -d' -su elasticsearch -c '/usr/share/elasticsearch/bin/elasticsearch -d' \ No newline at end of file diff --git a/assets/elasticsearch/stop-ela.sh b/assets/elasticsearch/stop-ela.sh deleted file mode 100644 index ed333e2..0000000 --- a/assets/elasticsearch/stop-ela.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -pkill -f java \ No newline at end of file diff --git a/assets/loki/loki_docker_run.sh b/assets/loki/loki_docker_run.sh deleted file mode 100644 index 387208b..0000000 --- a/assets/loki/loki_docker_run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Just make sure the loki-config.yaml under the same directory -docker run --name loki -d -v $(pwd):/mnt/config -p 3100:3100 grafana/loki:3.0.0 -config.file=/mnt/config/loki-config.yaml diff --git a/assets/loki/promtail_docker_run.sh b/assets/loki/promtail_docker_run.sh deleted file mode 100644 index 9c27ac2..0000000 --- a/assets/loki/promtail_docker_run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Replace the datasets to the actual path, also note that modify the promtail-config.yaml, since the data here is like hadoop-258GB/worker1/wroker1/[logs] -docker run --name promtail -d -v $(pwd):/mnt/config -v /home/xiaochong/clp-bench/tests/datasets/hadoop-258GB:/mnt/datasets/hadoop --link loki grafana/promtail:3.0.0 -config.file=/mnt/config/promtail-config.yaml diff --git a/assets/elasticsearch-unstructured/Dockerfile b/assets/unstructured/elasticsearch/Dockerfile similarity index 67% rename from assets/elasticsearch-unstructured/Dockerfile rename to assets/unstructured/elasticsearch/Dockerfile index 8424848..e9f6d23 100644 --- a/assets/elasticsearch-unstructured/Dockerfile +++ b/assets/unstructured/elasticsearch/Dockerfile @@ -5,17 +5,20 @@ RUN apt-get update \ curl \ gpg -RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | tee /etc/apt/sources.list.d/elastic-8.x.list +RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch \ + | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg]" \ + "https://artifacts.elastic.co/packages/8.x/apt stable main" \ + | tee /etc/apt/sources.list.d/elastic-8.x.list RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ - python3-venv \ - tmux \ - vim \ + elasticsearch=8.6.2 \ libcurl4 \ libcurl4-openssl-dev \ - elasticsearch=8.6.2 \ - python3-pip + python3-pip \ + python3-venv \ + tmux \ + vim RUN pip install elasticsearch requests numpy matplotlib diff --git a/assets/unstructured/elasticsearch/clear-cache.py b/assets/unstructured/elasticsearch/clear-cache.py new file mode 100644 index 0000000..045a43e --- /dev/null +++ b/assets/unstructured/elasticsearch/clear-cache.py @@ -0,0 +1,9 @@ +import os + +from elasticsearch import Elasticsearch + +collection_name = "elasticsearch_clp_bench" + +es = Elasticsearch("http://localhost:9202", timeout=30, max_retries=10, retry_on_timeout=True) +es.indices.clear_cache(index=collection_name) +os.system("sync; echo 1 > /proc/sys/vm/drop_caches") diff --git a/assets/unstructured/elasticsearch/clear-cache.sh b/assets/unstructured/elasticsearch/clear-cache.sh new file mode 100755 index 0000000..8065590 --- /dev/null +++ b/assets/unstructured/elasticsearch/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/clear-cache.py" diff --git a/assets/unstructured/elasticsearch/config.yaml b/assets/unstructured/elasticsearch/config.yaml new file mode 100644 index 0000000..77213df --- /dev/null +++ b/assets/unstructured/elasticsearch/config.yaml @@ -0,0 +1,56 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: elasticsearch-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets/worker*/worker*/*log* +hot_run_warm_up_times: 3 +related_processes: + - /usr/share/elasticsearch/jdk/bin/java + - /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller +queries: + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "}}}}, + "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: + Container "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="}}}}, + "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " to pid 21177 as user "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " 10000 reply: "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " 10 reply: "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " 178.2 MB "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " 1.9 GB "}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + "job_1528179349176_24837"}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + "blk_1075089282_1348458"}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + "hdfs://master:8200/HiBench/Bayes/temp/worddict"}}}}, "size": 10000}' + - > + '{"query": {"bool": {"must": {"match_phrase": {"log_line": + " abcde "}}}}, "size": 10000}' diff --git a/assets/unstructured/elasticsearch/container-name b/assets/unstructured/elasticsearch/container-name new file mode 100644 index 0000000..496abf7 --- /dev/null +++ b/assets/unstructured/elasticsearch/container-name @@ -0,0 +1 @@ +elasticsearch-clp-bench \ No newline at end of file diff --git a/assets/unstructured/elasticsearch/docker-build.sh b/assets/unstructured/elasticsearch/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/unstructured/elasticsearch/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/unstructured/elasticsearch/docker-run.sh b/assets/unstructured/elasticsearch/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/unstructured/elasticsearch/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/unstructured/elasticsearch/ingest.py b/assets/unstructured/elasticsearch/ingest.py new file mode 100644 index 0000000..d937143 --- /dev/null +++ b/assets/unstructured/elasticsearch/ingest.py @@ -0,0 +1,52 @@ +import glob +import logging +import sys + +import requests +from elasticsearch import Elasticsearch +from elasticsearch.helpers import streaming_bulk # type: ignore + +collection_name = "elasticsearch_clp_bench" + +# path_pattern = '/home/datasets/worker*/worker*/*log*' +path_pattern = sys.argv[1] +# Find all files matching the pattern +log_files = glob.glob(path_pattern) +logging.info(f"Total log files: {len(log_files)}") + + +def traverse_data(collection_name): + for log_file in log_files: + logging.info(f"Processed file: {log_file}") + with open(log_file, "r") as f: + for line in f.readlines(): + yield {"_index": collection_name, "_source": {"log_line": line}} + + +def ingest_dataset(): + es = Elasticsearch("http://localhost:9202", request_timeout=1200, retry_on_timeout=True) + + count = 0 + for success, info in streaming_bulk( + es, + traverse_data(collection_name), + raise_on_error=False, + raise_on_exception=False, + chunk_size=10000, + request_timeout=120, + ): + if success: + count += 1 + else: + logging.error(f"Failed to index document at {count}: {info}") + if count % 100000 == 0: + logging.info(f"Index {count} logs") + + requests.post(f"http://localhost:9202/{collection_name}/_flush/") + + +if __name__ == "__main__": + try: + ingest_dataset() + except Exception as e: + print(e) diff --git a/assets/unstructured/elasticsearch/ingest.sh b/assets/unstructured/elasticsearch/ingest.sh new file mode 100755 index 0000000..ce9d34e --- /dev/null +++ b/assets/unstructured/elasticsearch/ingest.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/ingest.py" "$1" diff --git a/assets/unstructured/elasticsearch/launch.sh b/assets/unstructured/elasticsearch/launch.sh new file mode 100755 index 0000000..ea6d4f1 --- /dev/null +++ b/assets/unstructured/elasticsearch/launch.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +sed -i "s/#http.port: 9200/http.port: 9202/" /etc/elasticsearch/elasticsearch.yml +sed -i "s/xpack.security.enabled: true/xpack.security.enabled: false/" \ + /etc/elasticsearch/elasticsearch.yml +sed -i "/cluster.initial_master_nodes/d" /etc/elasticsearch/elasticsearch.yml +grep -q "discovery.type: single-node" /etc/elasticsearch/elasticsearch.yml \ + || echo "discovery.type: single-node" >>/etc/elasticsearch/elasticsearch.yml + +sed -i "/elasticsearch/ s/\/bin\/false/\/bin\/bash/" /etc/passwd +sed -i "/elasticsearch/ s/\/nonexistent/\/usr\/share\/elasticsearch/" /etc/passwd + +# It seems the environment variables sometimes does not work, need stop and start the elasticsearch +# in the container manually to ensure it is applied use to check: curl -XGET \ +# "http://localhost:9202/_nodes/stats/jvm?pretty" +# ES_JAVA_OPTS="-Xms256m -Xmx256m" su elasticsearch -c \ +# "/usr/share/elasticsearch/bin/elasticsearch -d" +su elasticsearch -c "/usr/share/elasticsearch/bin/elasticsearch -d" diff --git a/assets/unstructured/elasticsearch/measure-compressed-size.py b/assets/unstructured/elasticsearch/measure-compressed-size.py new file mode 100644 index 0000000..f36a216 --- /dev/null +++ b/assets/unstructured/elasticsearch/measure-compressed-size.py @@ -0,0 +1,9 @@ +import time + +import requests + +collection_name = "elasticsearch_clp_bench" + +time.sleep(5) +response = requests.get(f"http://localhost:9202/{collection_name}/_stats").json() +print(response["_all"]["total"]["store"]["size_in_bytes"]) diff --git a/assets/unstructured/elasticsearch/measure-compressed-size.sh b/assets/unstructured/elasticsearch/measure-compressed-size.sh new file mode 100755 index 0000000..2d72df6 --- /dev/null +++ b/assets/unstructured/elasticsearch/measure-compressed-size.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}"/measure-compressed-size.py diff --git a/assets/unstructured/elasticsearch/measure-decompressed-size.sh b/assets/unstructured/elasticsearch/measure-decompressed-size.sh new file mode 100755 index 0000000..ecc1081 --- /dev/null +++ b/assets/unstructured/elasticsearch/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +bash -c "du $1 -bc" | awk "END {print \$1}" diff --git a/assets/unstructured/elasticsearch/methodology.md b/assets/unstructured/elasticsearch/methodology.md new file mode 100644 index 0000000..4bf48d7 --- /dev/null +++ b/assets/unstructured/elasticsearch/methodology.md @@ -0,0 +1,16 @@ +# Basic Information +| Version | Download Link (Image or Binary) | +|---------|---------------------------------| +| 8.6.2 | 💾[Download][download] | + +# Specifics +We deploy [elasticsearch] in a single-node configuration with the security feature of +[xpack][disabling-xpack] disabled. We use Elasticsearch's Python package for data ingestion and +search operations. + +In contrast to dynamically-structured benchmarking, unstructured datasets for Elasticsearch require no +preprocessing. + +[download]: https://www.elastic.co/downloads/past-releases/elasticsearch-8-6-2 +[disabling-xpack]: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html +[elasticsearch]: https://www.elastic.co/downloads/elasticsearch diff --git a/assets/unstructured/elasticsearch/reset.py b/assets/unstructured/elasticsearch/reset.py new file mode 100644 index 0000000..8d9edfa --- /dev/null +++ b/assets/unstructured/elasticsearch/reset.py @@ -0,0 +1,5 @@ +import requests + +collection_name = "elasticsearch_clp_bench" + +requests.delete(f"http://localhost:9202/{collection_name}") diff --git a/assets/unstructured/elasticsearch/reset.sh b/assets/unstructured/elasticsearch/reset.sh new file mode 100755 index 0000000..befe37c --- /dev/null +++ b/assets/unstructured/elasticsearch/reset.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}"/reset.py diff --git a/assets/unstructured/elasticsearch/results.json b/assets/unstructured/elasticsearch/results.json new file mode 100644 index 0000000..179958b --- /dev/null +++ b/assets/unstructured/elasticsearch/results.json @@ -0,0 +1,50 @@ +{ + "target": "elasticsearch", + "targetDisplayedName": "Elasticsearch", + "displayedOrder": 2, + "isEnable": true, + "type": 1, + "ingestTime": 117772160, + "compressedSize": 124610255258, + "avgIngestMem": 54708970455, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 14068779008, + "queryTimes": [ + 660, + 550, + 27860, + 42030, + 480, + 1010, + 100910, + 570, + 66480, + 520, + 560, + 8530, + 450 + ] + }, + { + "metric": 2, + "avgQueryMem": 14075236352, + "queryTimes": [ + 1620, + 3170, + 37300, + 47290, + 510, + 23820, + 107570, + 580, + 76530, + 470, + 590, + 10310, + 490 + ] + } + ] +} diff --git a/assets/unstructured/elasticsearch/search.py b/assets/unstructured/elasticsearch/search.py new file mode 100644 index 0000000..7780fa0 --- /dev/null +++ b/assets/unstructured/elasticsearch/search.py @@ -0,0 +1,47 @@ +import logging +import sys + +from elasticsearch import Elasticsearch + +collection_name = "elasticsearch_clp_bench" + +es = Elasticsearch( + "http://localhost:9202", request_timeout=30, max_retries=10, retry_on_timeout=True +) +results = [] + +query = sys.argv[1] + + +# Function to execute a query without cache +def execute_query_without_cache(query): + # Initialize the scroll + page = es.search( + index=collection_name, + scroll="8m", # Keep the search context open for 2 minutes + body=query, + request_cache=False, + ) + + for result in page["hits"]["hits"]: + results.append(result) + + # Start scrolling + sid = page["_scroll_id"] + while True: + page = es.scroll(scroll_id=sid, scroll="8m") + if not page["hits"]["hits"]: + break + for result in page["hits"]["hits"]: + results.append(result) + + +# Execute the query +while True: + try: + execute_query_without_cache(query) + print(len(results)) + break + except Exception as e: + logging.error(e) + continue diff --git a/assets/unstructured/elasticsearch/search.sh b/assets/unstructured/elasticsearch/search.sh new file mode 100755 index 0000000..d8243ed --- /dev/null +++ b/assets/unstructured/elasticsearch/search.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +python3 "${script_dir}/search.py" "$1" diff --git a/assets/unstructured/elasticsearch/terminate.sh b/assets/unstructured/elasticsearch/terminate.sh new file mode 100755 index 0000000..6f84db0 --- /dev/null +++ b/assets/unstructured/elasticsearch/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +pkill -f java diff --git a/assets/unstructured/glt/Dockerfile b/assets/unstructured/glt/Dockerfile new file mode 100644 index 0000000..6fd60f0 --- /dev/null +++ b/assets/unstructured/glt/Dockerfile @@ -0,0 +1,11 @@ +FROM ghcr.io/y-scope/clp/clp-core-dependencies-x86-ubuntu-jammy:main + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + htop \ + jq \ + python3-venv \ + rsync \ + sqlite3 \ + tmux \ + vim diff --git a/assets/unstructured/glt/clear-cache.sh b/assets/unstructured/glt/clear-cache.sh new file mode 100755 index 0000000..78387c4 --- /dev/null +++ b/assets/unstructured/glt/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/unstructured/glt/config.yaml b/assets/unstructured/glt/config.yaml new file mode 100644 index 0000000..9cb3a70 --- /dev/null +++ b/assets/unstructured/glt/config.yaml @@ -0,0 +1,28 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: clp-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets +hot_run_warm_up_times: 3 +related_processes: + - /home/assets/glt +queries: + - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' + - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' + - > + " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: + Container " + - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' + - '" to pid 21177 as user "' + - '" 10000 reply: "' + - '" 10 reply: "' + - '" 178.2 MB "' + - '" 1.9 GB "' + - '"job_1528179349176_24837"' + - '"blk_1075089282_1348458"' + - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' + - '" abcde "' diff --git a/assets/unstructured/glt/container-name b/assets/unstructured/glt/container-name new file mode 100644 index 0000000..45bfecc --- /dev/null +++ b/assets/unstructured/glt/container-name @@ -0,0 +1 @@ +clp-clp-bench \ No newline at end of file diff --git a/assets/unstructured/glt/docker-build.sh b/assets/unstructured/glt/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/unstructured/glt/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/unstructured/glt/docker-run.sh b/assets/unstructured/glt/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/unstructured/glt/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/unstructured/glt/ingest.sh b/assets/unstructured/glt/ingest.sh new file mode 100755 index 0000000..ef0be0e --- /dev/null +++ b/assets/unstructured/glt/ingest.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +glt_binary=/home/assets/glt +data_path=/home/archives + +"${glt_binary}" c "$data_path" "$1" diff --git a/assets/unstructured/glt/launch.sh b/assets/unstructured/glt/launch.sh new file mode 100755 index 0000000..a1ef9e1 --- /dev/null +++ b/assets/unstructured/glt/launch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +data_path=/home/archives + +mkdir -p "$data_path" diff --git a/assets/unstructured/glt/measure-compressed-size.sh b/assets/unstructured/glt/measure-compressed-size.sh new file mode 100755 index 0000000..288db18 --- /dev/null +++ b/assets/unstructured/glt/measure-compressed-size.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +data_path=/home/archives +find "$data_path" -exec chmod o+r+x {} \; +du "$data_path" -bc | awk "END {print \$1}" diff --git a/assets/unstructured/glt/measure-decompressed-size.sh b/assets/unstructured/glt/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/unstructured/glt/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/unstructured/glt/methodology.md b/assets/unstructured/glt/methodology.md new file mode 100644 index 0000000..f935181 --- /dev/null +++ b/assets/unstructured/glt/methodology.md @@ -0,0 +1,13 @@ +# Basic Information +| Version | Download Link (Image or Binary) | +|---------|---------------------------------| +| 0.2.0 | 💾[Download][download] | + +First, download the latest released binary, or clone the latest [code][clp] and compile it locally +by following these [instructions][core-build]. Then we use `glt` binary. No preprocessing is needed +for the raw data. + +[clp]: https://github.com/y-scope/clp +[core-build]: https://docs.yscope.com/clp/main/dev-guide/components-core/index.html +[download]: https://github.com/y-scope/clp/releases/tag/v0.2.0 +[glt]: https://docs.yscope.com/clp/main/user-guide/core-unstructured/glt.html diff --git a/assets/unstructured/glt/reset.sh b/assets/unstructured/glt/reset.sh new file mode 100755 index 0000000..87f33bc --- /dev/null +++ b/assets/unstructured/glt/reset.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +data_path=/home/archives + +rm -rf "$data_path" +mkdir -p "$data_path" diff --git a/assets/unstructured/glt/results.json b/assets/unstructured/glt/results.json new file mode 100644 index 0000000..96e3e22 --- /dev/null +++ b/assets/unstructured/glt/results.json @@ -0,0 +1,50 @@ +{ + "target": "glt", + "targetDisplayedName": "CLP", + "displayedOrder": 1, + "isEnable": true, + "type": 1, + "ingestTime": 3161250, + "compressedSize": 3758274642, + "avgIngestMem": 1486115308, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 1712198779, + "queryTimes": [ + 2620, + 2400, + 2690, + 3280, + 2260, + 55270, + 54100, + 2330, + 4590, + 5250, + 4610, + 4150, + 1870 + ] + }, + { + "metric": 2, + "avgQueryMem": 1149728768, + "queryTimes": [ + 2844, + 2576, + 2728, + 3359, + 2434, + 55033, + 57339, + 2715, + 4795, + 5934, + 5364, + 4656, + 2016 + ] + } + ] +} diff --git a/assets/unstructured/glt/search.sh b/assets/unstructured/glt/search.sh new file mode 100755 index 0000000..e47cfb6 --- /dev/null +++ b/assets/unstructured/glt/search.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +glt_binary=/home/assets/glt +data_path=/home/archives + +"${glt_binary}" s "$data_path" "$1" | wc -l diff --git a/assets/unstructured/glt/terminate.sh b/assets/unstructured/glt/terminate.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/unstructured/glt/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/unstructured/grep/Dockerfile b/assets/unstructured/grep/Dockerfile new file mode 100644 index 0000000..a0fc862 --- /dev/null +++ b/assets/unstructured/grep/Dockerfile @@ -0,0 +1 @@ +FROM ubuntu:latest diff --git a/assets/unstructured/grep/clear-cache.sh b/assets/unstructured/grep/clear-cache.sh new file mode 100755 index 0000000..78387c4 --- /dev/null +++ b/assets/unstructured/grep/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/unstructured/grep/config.yaml b/assets/unstructured/grep/config.yaml new file mode 100644 index 0000000..ad264c2 --- /dev/null +++ b/assets/unstructured/grep/config.yaml @@ -0,0 +1,28 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: grep-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets +hot_run_warm_up_times: 3 +related_processes: + - grep +queries: + - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' + - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' + - > + " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: + Container " + - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' + - '" to pid 21177 as user "' + - '" 10000 reply: "' + - '" 10 reply: "' + - '" 178.2 MB "' + - '" 1.9 GB "' + - '"job_1528179349176_24837"' + - '"blk_1075089282_1348458"' + - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' + - '" abcde "' diff --git a/assets/unstructured/grep/container-name b/assets/unstructured/grep/container-name new file mode 100644 index 0000000..84fcefe --- /dev/null +++ b/assets/unstructured/grep/container-name @@ -0,0 +1 @@ +grep-clp-bench \ No newline at end of file diff --git a/assets/unstructured/grep/docker-build.sh b/assets/unstructured/grep/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/unstructured/grep/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/unstructured/grep/docker-run.sh b/assets/unstructured/grep/docker-run.sh new file mode 100755 index 0000000..0a37dae --- /dev/null +++ b/assets/unstructured/grep/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/unstructured/grep/ingest.sh b/assets/unstructured/grep/ingest.sh new file mode 100755 index 0000000..13697a0 --- /dev/null +++ b/assets/unstructured/grep/ingest.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +echo "Nothing to do..." diff --git a/assets/unstructured/grep/launch.sh b/assets/unstructured/grep/launch.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/unstructured/grep/launch.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/unstructured/grep/measure-compressed-size.sh b/assets/unstructured/grep/measure-compressed-size.sh new file mode 100755 index 0000000..6f4bb17 --- /dev/null +++ b/assets/unstructured/grep/measure-compressed-size.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +datasets_path=/home/datasets +du "$datasets_path" -bc | awk "END {print \$1}" diff --git a/assets/unstructured/grep/measure-decompressed-size.sh b/assets/unstructured/grep/measure-decompressed-size.sh new file mode 100755 index 0000000..7d306be --- /dev/null +++ b/assets/unstructured/grep/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/unstructured/grep/methodology.md b/assets/unstructured/grep/methodology.md new file mode 100644 index 0000000..39144a6 --- /dev/null +++ b/assets/unstructured/grep/methodology.md @@ -0,0 +1,9 @@ +# Basic Information +| Version | Download Link (Image or Binary) | +|---------|---------------------------------| +| 3.7 | 💾[Download][download] | + +_grep_ is a command-line tool in Unix/Linux systems used to search for specific patterns within +files or text. We use it as a baseline of unstructured log query benchmark. + +[download]: https://ftp.gnu.org/gnu/grep/grep-3.7.tar.gz diff --git a/assets/unstructured/grep/reset.sh b/assets/unstructured/grep/reset.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/unstructured/grep/reset.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/unstructured/grep/results.json b/assets/unstructured/grep/results.json new file mode 100644 index 0000000..923c947 --- /dev/null +++ b/assets/unstructured/grep/results.json @@ -0,0 +1,50 @@ +{ + "target": "grep", + "targetDisplayedName": "grep", + "displayedOrder": 999, + "isEnable": true, + "type": 1, + "ingestTime": 0, + "compressedSize": 0, + "avgIngestMem": 0, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 0, + "queryTimes": [ + 494670, + 514690, + 403070, + 373030, + 540660, + 666190, + 604870, + 640610, + 798320, + 443490, + 839100, + 670170, + 394690 + ] + }, + { + "metric": 2, + "avgQueryMem": 0, + "queryTimes": [ + 494670, + 514690, + 403070, + 373030, + 540660, + 666190, + 604870, + 640610, + 798320, + 443490, + 839100, + 670170, + 394690 + ] + } + ] +} diff --git a/assets/unstructured/grep/search.sh b/assets/unstructured/grep/search.sh new file mode 100755 index 0000000..9a877cf --- /dev/null +++ b/assets/unstructured/grep/search.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +datasets_path=/home/datasets +grep -r "$1" "$datasets_path" | wc -l diff --git a/assets/unstructured/grep/terminate.sh b/assets/unstructured/grep/terminate.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/unstructured/grep/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/assets/loki/loki-config.yaml "b/assets/unstructured/loki\360\237\232\247/loki-config.yaml" similarity index 100% rename from assets/loki/loki-config.yaml rename to "assets/unstructured/loki\360\237\232\247/loki-config.yaml" diff --git "a/assets/unstructured/loki\360\237\232\247/loki-docker-run.sh" "b/assets/unstructured/loki\360\237\232\247/loki-docker-run.sh" new file mode 100755 index 0000000..180bac6 --- /dev/null +++ "b/assets/unstructured/loki\360\237\232\247/loki-docker-run.sh" @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Just make sure the loki-config.yaml under the same directory +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +docker rm -f loki +docker run \ + --name loki \ + -d \ + -v "$script_dir":/mnt/config \ + -p 3100:3100 grafana/loki:3.0.0 \ + -config.file=/mnt/config/loki-config.yaml diff --git "a/assets/unstructured/loki\360\237\232\247/methodology.md" "b/assets/unstructured/loki\360\237\232\247/methodology.md" new file mode 100644 index 0000000..42da7cf --- /dev/null +++ "b/assets/unstructured/loki\360\237\232\247/methodology.md" @@ -0,0 +1,188 @@ +# Basic Information +| Version | Download Link (Image or Binary) | +|---------|---------------------------------| +| 3.0.0 | 💾[Download][download] | + +# Specifics +[loki] runs with two microservices. Loki itself is working as a backend which ingests the data sent +by the log collector. We use [promtail] as its log collector. These two are running in two +containers communicated through REST APIs. For query benchmark, we use [logcli] to execute queries. + +We haven't integrated launching and ingesting for Loki into `clp-bench` yet, so you may need to +manually launch and ingest data first, then use `clp-bench` to run the query benchmark. + +## Launch + +To run Loki, create a `loki-config.yaml` configuration file as the following (see details +[here][loki-config]): + +```yaml +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + +common: + instance_addr: 127.0.0.1 + path_prefix: /tmp/loki + storage: + filesystem: + chunks_directory: /tmp/loki/chunks + rules_directory: /tmp/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 1000 + +schema_config: + configs: + - from: 1972-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +limits_config: + reject_old_samples: false + ingestion_rate_mb: 1000 + +ruler: + alertmanager_url: http://localhost:9093 +``` + +Then launch a container for Loki with the following command (assuming you are currently at the +directory which contains the `yaml` configuration file above): + +```shell +docker run \ + --name loki \ + -d \ + -v $(pwd):/mnt/config \ + -p 3100:3100 \ + grafana/loki:3.0.0 \ + -config.file=/mnt/config/loki-config.yaml +``` + +To run Promtail, create a `promtail-config.yaml` file as the following: + +```yaml +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: system + static_configs: + - targets: + - localhost + labels: + job: benchlogs + __path__: /mnt/datasets/hadoop/worker*/* +``` + +Note that `__path__` should be the pattern of directories which contain the log files. + +Then launch a container for Promtail with the following command (assuming you are currently at the +directory which contains the above mentioned `yaml` configuration file): + +```shell +docker run \ + --name promtail \ + -d \ + -v $(pwd):/mnt/config \ + -v /path/to/hadoop-log-datasets:/mnt/datasets/hadoop \ + --link loki \ + grafana/promtail:3.0.0 \ + -config.file=/mnt/config/promtail-config.yaml +``` + +When containers for Loki and Promtail have been launched, run the following command until it prints +`ready` , then Loki will start ingesting data: + +```shell +curl -G http://localhost:3100/ready +``` + +## Ingest + +Loki ingests data automatically when connects to Promtail. We do not do any preprocessing for the +dataset. + +We use `docker stats` to get the memory usage of ingesting data periodically (the frequency should +be consistent with the `yaml` configuration file for `clp-bench as mentioned in the next section) +until the ingestion finishes: + +```shell +docker stats loki promtail --no-stream +``` + +Since Loki does not have prompt when ingestion finishes, we monitor the current ingested data by the +following command: + +```shell +curl -G http://localhost:3100/metrics | grep 'loki_distributor_bytes_received_total' +``` + +When the above command prints the size that equals to the size of the dataset, it means the +ingestion finishes. Then we run the following command to get the time spent for ingesting data: + +```shell +curl -G http://localhost:3100/metrics | \ + grep 'loki_request_duration_seconds_sum{method="POST",route="loki_api_v1_push"' +``` + +## Query Benchmarking + +The configuration of query benchmarking is in `search.py` . + +Note that in the configuration: + +- `job` should match the `job` of the `labels` in the `promtail-config.yaml` (see the example of + `promtail-config.yaml` ). +- `limit` should be at least the maximum number of matched log lines of the query. +- `batch` is maximum number of matched log lines that Loki will send to the client at once. +- `from_ts` and `to_ts` define the rough wall-clock time range of ingesting data. In this example, + it means that data ingestion happened between `2024-10-08T10:00:00Z` and `2024-10-09T10:00:00Z` . +- `interval` defines the time range, in minutes, that Loki will use to query log lines ingested + within that period. For example, setting an interval of 30 means the time range between from and + to will be divided into 30-minute slices. Loki will run the query on log lines ingested during + each of these slices. For each query, `clp-bench` instructs Loki to execute the query across all + time slices, ensuring the entire dataset is covered. + +For each query, `clp-bench` run the following command. Note that the `time_slice_start` and +`time_slice_to` are the start and end timestamps for each time slice, `clp-bench` will iterate all +time slices between `from` and `to` in the configuration to cover the entire dataset. + +```shell + \ + query '{ job="{job}" } |~ {query}' \ + --limit={limit} \ + --batch={batch} \ + --from="{time_slice_start}" \ + --to="{time_slice_to}" +``` + +For measuring memory usage during query execution, we employ the same method used for data +ingestion. + +[download]: https://github.com/grafana/loki/releases/tag/v3.0.0 +[logcli]: https://grafana.com/docs/loki/latest/query/logcli/ +[loki-config]: https://grafana.com/docs/loki/latest/configure/ +[loki]: https://grafana.com/oss/loki/ +[promtail]: https://grafana.com/docs/loki/latest/send-data/promtail/ diff --git a/assets/loki/promtail-config.yaml "b/assets/unstructured/loki\360\237\232\247/promtail-config.yaml" similarity index 100% rename from assets/loki/promtail-config.yaml rename to "assets/unstructured/loki\360\237\232\247/promtail-config.yaml" diff --git "a/assets/unstructured/loki\360\237\232\247/promtail-docker-run.sh" "b/assets/unstructured/loki\360\237\232\247/promtail-docker-run.sh" new file mode 100755 index 0000000..89b99ff --- /dev/null +++ "b/assets/unstructured/loki\360\237\232\247/promtail-docker-run.sh" @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Replace the datasets to the actual path, also note that modify the promtail-config.yaml, since the data here is like hadoop-258GB/worker1/wroker1/[logs] +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +docker rm -f promtail +docker run \ + --name promtail \ + -d \ + -v "${script_dir}":/mnt/config \ + -v "$1":/mnt/datasets/hadoop \ + --link loki grafana/promtail:3.0.0 \ + -config.file=/mnt/config/promtail-config.yaml diff --git "a/assets/unstructured/loki\360\237\232\247/results.json" "b/assets/unstructured/loki\360\237\232\247/results.json" new file mode 100644 index 0000000..adf982d --- /dev/null +++ "b/assets/unstructured/loki\360\237\232\247/results.json" @@ -0,0 +1,50 @@ +{ + "target": "loki", + "targetDisplayedName": "Loki", + "displayedOrder": 3, + "isEnable": true, + "type": 1, + "ingestTime": 8661810, + "compressedSize": 26788642161, + "avgIngestMem": 43078521979, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 25583723479, + "queryTimes": [ + 651500, + 764890, + 1546260, + 4950080, + 517290, + 509460, + 2790890, + 576470, + 3362330, + 450830, + 435630, + 940180, + 467020 + ] + }, + { + "metric": 2, + "avgQueryMem": 26403311452, + "queryTimes": [ + 658560, + 639740, + 1403050, + 4655970, + 389030, + 364110, + 2608620, + 412570, + 3404270, + 108990, + 108410, + 825040, + 367430 + ] + } + ] +} diff --git "a/assets/unstructured/loki\360\237\232\247/search.py" "b/assets/unstructured/loki\360\237\232\247/search.py" new file mode 100755 index 0000000..8cf3acc --- /dev/null +++ "b/assets/unstructured/loki\360\237\232\247/search.py" @@ -0,0 +1,77 @@ +import os +import subprocess +import time +from datetime import timedelta + +from dateutil import parser + +# When launched the loki and promtail containers, the ingestion is automatically started +# You could query the current ingested bytes by: +# curl -G http://localhost:3100/metrics | grep 'loki_distributor_bytes_received_total' +# You could query the current compression size by: +# curl -G http://localhost:3100/metrics | grep 'loki_chunk_store_stored_chunk_bytes_total' +# You could query the ingestion time by: +# curl -G http://localhost:3100/metrics | \ +# grep 'loki_request_duration_seconds_sum{method="POST",route="loki_api_v1_push"' + +script_dir = os.path.dirname(os.path.realpath(__file__)) +logcli_binary_path = os.path.join(script_dir, "logcli") +job = "benchlogs" +limit = 2000000 +batch = 1000 +# The from and to is the time range of compression. For example, compression started after +# 2024-11-03T14:00:00Z and ended before 2024-11-05T16:00:00Z, then from_ts and to_ts will +# be as following +from_ts = "2024-11-03T14:00:00Z" +to_ts = "2024-11-05T16:00:00Z" +interval = timedelta(minutes=30) +queries = [ + '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "', + '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "', + '" INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: ' + 'Container "', + '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="', + '" to pid 21177 as user "', + '" 10000 reply: "', + '" 10 reply: "', + '" 178.2 MB "', + '" 1.9 GB "', + '"job_1528179349176_24837"', + '"blk_1075089282_1348458"', + '"hdfs://master:8200/HiBench/Bayes/temp/worddict"', + '" abcde "', +] + +start_time = parser.isoparse(from_ts) +end_time = parser.isoparse(to_ts) +for query in queries: + current_time = start_time + total_query_latency = 0 + total_nr_matched_log_lines = 0 + while current_time <= end_time - interval: + command = ( + f"{logcli_binary_path} query " + + "'{ job=" + + f'"{job}"' + + "} |~ " + + query + + f"' --limit={limit} --batch={batch} " + + f'--from="{current_time.isoformat()}" ' + + f'--to="{(current_time + interval).isoformat()}" | wc -l' + ) + start_ts = time.perf_counter_ns() + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + shell=True, + check=True, + ) + end_ts = time.perf_counter_ns() + total_query_latency += (end_ts - start_ts) / 1e9 + total_nr_matched_log_lines += int(result.stdout.decode("utf-8").strip()) + current_time += interval + print( + f"Query: {query}, matched log lines: {total_nr_matched_log_lines}, e2e latency: " + f"{total_query_latency}" + ) diff --git a/assets/unstructured/splunk/Dockerfile b/assets/unstructured/splunk/Dockerfile new file mode 100644 index 0000000..14d391d --- /dev/null +++ b/assets/unstructured/splunk/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:20.04 + +COPY splunk.deb /tmp/splunk.deb + +# Update and install basic packages +RUN apt-get update && apt-get install -y \ + /tmp/splunk.deb \ + curl \ + wget \ + vim && \ + rm -f /tmp/splunk.deb diff --git a/assets/unstructured/splunk/clear-cache.sh b/assets/unstructured/splunk/clear-cache.sh new file mode 100755 index 0000000..78387c4 --- /dev/null +++ b/assets/unstructured/splunk/clear-cache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/unstructured/splunk/config.yaml b/assets/unstructured/splunk/config.yaml new file mode 100644 index 0000000..f35417d --- /dev/null +++ b/assets/unstructured/splunk/config.yaml @@ -0,0 +1,33 @@ +system_metric: + enable: true + memory: + ingest_polling_interval: 5 + run_query_benchmark_polling_interval: 5 + +container_id: splunk-clp-bench +assets_path: /home/assets +datasets_path: /home/datasets +hot_run_warm_up_times: 3 +related_processes: + - splunkd + - '[splunkd pid=' + - mongod + - compsup + - /opt/splunk + - splunk-optimize +queries: + - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' + - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' + - > + " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: + Container " + - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str\="' + - '" to pid 21177 as user "' + - '" 10000 reply: "' + - '" 10 reply: "' + - '" 178.2 MB "' + - '" 1.9 GB "' + - '"job_1528179349176_24837"' + - '"blk_1075089282_1348458"' + - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' + - '" abcde "' \ No newline at end of file diff --git a/assets/unstructured/splunk/container-name b/assets/unstructured/splunk/container-name new file mode 100644 index 0000000..21bd2fa --- /dev/null +++ b/assets/unstructured/splunk/container-name @@ -0,0 +1 @@ +splunk-clp-bench \ No newline at end of file diff --git a/assets/unstructured/splunk/docker-build.sh b/assets/unstructured/splunk/docker-build.sh new file mode 100755 index 0000000..bd2980a --- /dev/null +++ b/assets/unstructured/splunk/docker-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/unstructured/splunk/docker-run.sh b/assets/unstructured/splunk/docker-run.sh new file mode 100755 index 0000000..162704b --- /dev/null +++ b/assets/unstructured/splunk/docker-run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + --rm \ + -it \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/unstructured/splunk/ingest.sh b/assets/unstructured/splunk/ingest.sh new file mode 100755 index 0000000..9aa560c --- /dev/null +++ b/assets/unstructured/splunk/ingest.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +nr_log_files=$(find datasets/ -type f -name "*log*" | wc -l) +/opt/splunk/bin/splunk add monitor "$1" -auth "admin:admin_password" + +while true; do + count=$( + grep "Batch input finished reading file=" /opt/splunk/var/log/splunk/splunkd.log \ + | grep "$1" \ + | sed -n "s/.*file='\([^']*\)'.*/\1/p" \ + | wc -l + ) + + if [ "$count" -eq "$nr_log_files" ]; then + exit 0 + fi + + sleep 1 +done diff --git a/assets/unstructured/splunk/launch.sh b/assets/unstructured/splunk/launch.sh new file mode 100755 index 0000000..559b5d3 --- /dev/null +++ b/assets/unstructured/splunk/launch.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +if test -e /opt/splunk/ftr; then + echo "The ftr file exists. Splunk may not have been set up yet." + echo "[user_info]" >/opt/splunk/etc/system/local/user-seed.conf + echo "USERNAME = admin" >>/opt/splunk/etc/system/local/user-seed.conf + echo "PASSWORD = admin_password" >>/opt/splunk/etc/system/local/user-seed.conf + + echo "" >>/opt/splunk/etc/splunk-launch.conf + echo "OPTIMISTIC_ABOUT_FILE_LOCKING=1" >>/opt/splunk/etc/splunk-launch.conf + + { + echo "" + echo "[default]" + echo "TRUNCATE = 0" + echo "BREAK_ONLY_BEFORE_DATE = false" + echo "SHOULD_LINEMERGE = false" + } >>/opt/splunk/etc/apps/SplunkDeploymentServerConfig/default/props.conf + + { + echo "" + echo "[default]" + echo "crcSalt = " + echo "move_policy = sinkhole" + } >>/opt/splunk/etc/apps/SplunkDeploymentServerConfig/default/inputs.conf + + /opt/splunk/bin/splunk start --no-prompt --answer-yes --accept-license +else + echo "The ftr file does not exist. Splunk has likely been set up before." + /opt/splunk/bin/splunk start --no-prompt --answer-yes --accept-license +fi diff --git a/assets/unstructured/splunk/measure-compressed-size.sh b/assets/unstructured/splunk/measure-compressed-size.sh new file mode 100755 index 0000000..3bf522d --- /dev/null +++ b/assets/unstructured/splunk/measure-compressed-size.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +/opt/splunk/bin/splunk search "| dbinspect index=main | stats sum(sizeOnDiskMB)" \ + -auth "admin:admin_password" 2>/dev/null \ + | awk 'END {printf "%.0f\n", $1 * 1024 * 1024}' diff --git a/assets/unstructured/splunk/measure-decompressed-size.sh b/assets/unstructured/splunk/measure-decompressed-size.sh new file mode 100755 index 0000000..ecc1081 --- /dev/null +++ b/assets/unstructured/splunk/measure-decompressed-size.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +bash -c "du $1 -bc" | awk "END {print \$1}" diff --git a/assets/unstructured/splunk/methodology.md b/assets/unstructured/splunk/methodology.md new file mode 100644 index 0000000..290a194 --- /dev/null +++ b/assets/unstructured/splunk/methodology.md @@ -0,0 +1,48 @@ +# Basic Information +| Version | Download Link (Image or Binary) | +|---------|---------------------------------| +| 9.3.2 | 💾[Download][download] | + +# Specifics +We removed the event length restriction (`TRUNCATE = 0`) and configured each log line as an +individual event (`BREAK_ONLY_BEFORE_DATE = false` and `SHOULD_LINEMERGE = false`). + +Data ingestion is handled using `add monitor ${datasets}`. However, we noticed a discrepancy between +the raw data size (257.25 GB) and the size ingested by Splunk (255.61 GB), resulting in a data loss +of 1.64 GB. + +Upon investigation, we found that Splunk overlooked certain files during ingestion: +``` +/home/datasets/worker1/worker1/SecurityAuth-root.audit +/home/datasets/worker1/worker1/hadoop-root-datanode-8dc59161430b.out +/home/datasets/worker1/worker1/hadoop-root-datanode-8dc59161430b.out.1 +/home/datasets/worker1/worker1/hadoop-root-datanode-8dc59161430b.out.2 +/home/datasets/worker1/worker1/hadoop-root-datanode-8dc59161430b.out.3 +/home/datasets/worker1/worker1/yarn-root-nodemanager-8dc59161430b.out +/home/datasets/worker1/worker1/yarn-root-nodemanager-8dc59161430b.out.1 +/home/datasets/worker1/worker1/yarn-root-nodemanager-8dc59161430b.out.2 +/home/datasets/worker1/worker1/yarn-root-nodemanager-8dc59161430b.out.3 +/home/datasets/worker2/worker2/SecurityAuth-root.audit +/home/datasets/worker2/worker2/hadoop-root-datanode-fc8782b06de1.out +/home/datasets/worker2/worker2/hadoop-root-datanode-fc8782b06de1.out.1 +/home/datasets/worker2/worker2/hadoop-root-datanode-fc8782b06de1.out.2 +/home/datasets/worker2/worker2/hadoop-root-datanode-fc8782b06de1.out.3 +/home/datasets/worker2/worker2/yarn-root-nodemanager-fc8782b06de1.out +/home/datasets/worker2/worker2/yarn-root-nodemanager-fc8782b06de1.out.1 +/home/datasets/worker2/worker2/yarn-root-nodemanager-fc8782b06de1.out.2 +/home/datasets/worker2/worker2/yarn-root-nodemanager-fc8782b06de1.out.3 +/home/datasets/worker3/worker3/SecurityAuth-root.audit +/home/datasets/worker3/worker3/hadoop-root-datanode-8ed8bf9ebb0e.out +/home/datasets/worker3/worker3/hadoop-root-datanode-8ed8bf9ebb0e.out.1 +/home/datasets/worker3/worker3/hadoop-root-datanode-8ed8bf9ebb0e.out.2 +/home/datasets/worker3/worker3/hadoop-root-datanode-8ed8bf9ebb0e.out.3 +/home/datasets/worker3/worker3/yarn-root-nodemanager-8ed8bf9ebb0e.out +/home/datasets/worker3/worker3/yarn-root-nodemanager-8ed8bf9ebb0e.out.1 +/home/datasets/worker3/worker3/yarn-root-nodemanager-8ed8bf9ebb0e.out.2 +/home/datasets/worker3/worker3/yarn-root-nodemanager-8ed8bf9ebb0e.out.3 +``` + +The total size of these files is 26.21 KB. The most of remaining of missing data is still +unaccounted for, but they have no impact on query results of the queries we use during benchmarking. + +[download]: https://www.splunk.com/en_us/download/splunk-enterprise.html diff --git a/assets/unstructured/splunk/reset.sh b/assets/unstructured/splunk/reset.sh new file mode 100755 index 0000000..9877416 --- /dev/null +++ b/assets/unstructured/splunk/reset.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +bash "${script_dir}/terminate.sh" +/opt/splunk/bin/splunk clean eventdata -f +bash "${script_dir}/launch.sh" +/opt/splunk/bin/splunk clean kvstore -all -f -auth "admin:admin_password" diff --git a/assets/unstructured/splunk/results.json b/assets/unstructured/splunk/results.json new file mode 100644 index 0000000..7461dfb --- /dev/null +++ b/assets/unstructured/splunk/results.json @@ -0,0 +1,50 @@ +{ + "target": "splunk", + "targetDisplayedName": "Splunk", + "displayedOrder": 4, + "isEnable": true, + "type": 1, + "ingestTime": 34170500, + "compressedSize": 95267213312, + "avgIngestMem": 702476288, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 1019698871, + "queryTimes": [ + 1087, + 1490, + 44360, + 39494, + 1201, + 6662, + 226830, + 10202, + 17155, + 1213, + 1133, + 12280, + 988 + ] + }, + { + "metric": 2, + "avgQueryMem": 738287178, + "queryTimes": [ + 9969, + 86922, + 244437, + 186230, + 19150, + 174252, + 652198, + 137048, + 196954, + 16994, + 5579, + 172426, + 4477 + ] + } + ] +} diff --git a/assets/unstructured/splunk/search.sh b/assets/unstructured/splunk/search.sh new file mode 100755 index 0000000..e69c4e3 --- /dev/null +++ b/assets/unstructured/splunk/search.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +/opt/splunk/bin/splunk search "index=main \"$1\" | stats count" \ + -auth "admin:admin_password" \ + -preview false 2>/dev/null \ + | awk "END {print \$1}" diff --git a/assets/unstructured/splunk/terminate.sh b/assets/unstructured/splunk/terminate.sh new file mode 100755 index 0000000..8244443 --- /dev/null +++ b/assets/unstructured/splunk/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +/opt/splunk/bin/splunk stop diff --git a/assets/unstructured/template/Dockerfile b/assets/unstructured/template/Dockerfile new file mode 100644 index 0000000..ee1e4eb --- /dev/null +++ b/assets/unstructured/template/Dockerfile @@ -0,0 +1,11 @@ +# This file is used for building the container, ensuring installation of the required tool and +# dependencies + +# If there is any dedicated image available, you should build the benchmarking image on top of that +FROM ghcr.io/y-scope/clp/clp-core-dependencies-x86-ubuntu-jammy:main + +# Install necessary packages +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + tmux \ + vim diff --git a/assets/unstructured/template/clear-cache.sh b/assets/unstructured/template/clear-cache.sh new file mode 100755 index 0000000..b10a69d --- /dev/null +++ b/assets/unstructured/template/clear-cache.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# This script clears the tool's cache, essential for cold runs +# +# If there is any internal cache mechanism of the application, you should also clear them here +# (or turn it off when launching it) + +sync +echo 1 >/proc/sys/vm/drop_caches diff --git a/assets/unstructured/template/config.yaml b/assets/unstructured/template/config.yaml new file mode 100644 index 0000000..7a756ad --- /dev/null +++ b/assets/unstructured/template/config.yaml @@ -0,0 +1,58 @@ +# This config file contains essential benchmarking configurations + +system_metric: + # Toggle to enable system metric monitoring (e.g., memory usage). Set to `true` to activate + enable: true + memory: + # Time interval (in seconds) for polling memory during data ingestion + ingest_polling_interval: 5 + # Time interval (in seconds) for polling memory during query benchmarking + run_query_benchmark_polling_interval: 5 + +# Identifier for the benchmark container. Usually `${tool}-clp-bench` +container_id: {Your Tool Name}-clp-bench +# Path to the assets directory in the container. Leave as default unless modifying `docker-run.sh` +assets_path: /home/assets +# Path for datasets in the container; may refer to a file, directory, or file pattern. clp-bench +# does not validate the dataset's presence +datasets_path: /home/datasets +# Number of repetitions for query warm-up in hot-run mode before measuring latency. This may be +# automated in the future +hot_run_warm_up_times: 3 +# List of command substrings (the first substring of the entire command split by space from `ps +# aux`) to track relevant memory usage +related_processes: + - {Related Process Headers} +# Array of queries for benchmarking. Ensure escape characters are carefully handled. Note that each +# query should be wrapped with double quotes (or single quote, depends on whether the query contains +# double quotes) as the query will first be passed as an argument of `docker exec` rather than +# directly to the scripts +queries: + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - > + " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: + Container " + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" to pid 21177 as user "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" 10000 reply: "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" 10 reply: "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" 178.2 MB "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" 1.9 GB "' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '"job_1528179349176_24837"' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '"blk_1075089282_1348458"' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' + # Query all log lines that contains the given string (note the spaces at the beginning and the end + - '" abcde "' diff --git a/assets/unstructured/template/container-name b/assets/unstructured/template/container-name new file mode 100644 index 0000000..d0d9686 --- /dev/null +++ b/assets/unstructured/template/container-name @@ -0,0 +1 @@ +{Your Tool Name}-clp-bench \ No newline at end of file diff --git a/assets/unstructured/template/docker-build.sh b/assets/unstructured/template/docker-build.sh new file mode 100755 index 0000000..613b170 --- /dev/null +++ b/assets/unstructured/template/docker-build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script builds the container as per the `Dockerfile` in the same directory. Usually, only the +# `container_name` variable should be adjusted to match the `container_id` in `config.yaml` + +set -e + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") + +docker build \ + --tag "$container_name" \ + "$script_dir" diff --git a/assets/unstructured/template/docker-run.sh b/assets/unstructured/template/docker-run.sh new file mode 100755 index 0000000..ada9e03 --- /dev/null +++ b/assets/unstructured/template/docker-run.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# This script runs the container, taking the dataset path as an argument. Typically, only the +# `container_name` variable needs alignment with `container_id` in `config.yaml` + +set -e + +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./docker-run.sh " + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +container_name=$(cat "$script_dir/container-name") +workdir=/home + +docker run \ + --privileged \ + -it \ + --rm \ + --workdir "$workdir" \ + --network host \ + --name "$container_name" \ + --mount "type=bind,src=$script_dir,dst=/home/assets" \ + --mount "type=bind,src=$1,dst=/home/datasets" \ + "$container_name" \ + bash -c "cd ${workdir} && /bin/bash -l" diff --git a/assets/unstructured/template/ingest.sh b/assets/unstructured/template/ingest.sh new file mode 100755 index 0000000..6c40a90 --- /dev/null +++ b/assets/unstructured/template/ingest.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script handles data ingestion, with clp-bench measuring the total latency of this script. +# Avoid adding extra operations + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./ingest.sh " + exit 1 +fi + +# Your ingest command goes here diff --git a/assets/unstructured/template/launch.sh b/assets/unstructured/template/launch.sh new file mode 100755 index 0000000..de03e76 --- /dev/null +++ b/assets/unstructured/template/launch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This script initializes and starts the tool (e.g., if it functions as a server or service) + +# Your launch command goes here diff --git a/assets/unstructured/template/measure-compressed-size.sh b/assets/unstructured/template/measure-compressed-size.sh new file mode 100755 index 0000000..8d1ef83 --- /dev/null +++ b/assets/unstructured/template/measure-compressed-size.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This script measures the compressed data size post-ingestion, usually via tool-specific methods + +# Your archive measuring command goes here diff --git a/assets/unstructured/template/measure-decompressed-size.sh b/assets/unstructured/template/measure-decompressed-size.sh new file mode 100755 index 0000000..9198ff6 --- /dev/null +++ b/assets/unstructured/template/measure-decompressed-size.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This script measures the raw dataset size before ingestion. Typically unchanged, it takes +# `datasets_path` from `config.yaml` and uses `du -bc` for size calculation in bytes + +set -e +if [ -z "$1" ]; then + echo "Error: Datasets path argument is missing." + echo "Usage: bash ./measure-decompressed-size.sh " + exit 1 +fi + +du "$1" -bc | awk "END {print \$1}" diff --git a/assets/unstructured/template/methodology.md b/assets/unstructured/template/methodology.md new file mode 100644 index 0000000..367229f --- /dev/null +++ b/assets/unstructured/template/methodology.md @@ -0,0 +1,16 @@ +# Tool name methodology + +## Basics + +Version: [1.0.0][download] + +## Setup + +Describe any manual set up steps necessary for the tool. + +## Specifics + +Describe any specific tuning, preprocessing or configuration that beyond the defaults you made for +benchmarking. If there is no specifics you can delete this section. + +[download]: https://via.placeholder.com/20 diff --git a/assets/unstructured/template/reset.sh b/assets/unstructured/template/reset.sh new file mode 100755 index 0000000..132e4ea --- /dev/null +++ b/assets/unstructured/template/reset.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# This file prepares a clean environment by removing previous data (e.g., dropping tables); runs +# after `launch.sh` in `ingest` mode diff --git a/assets/unstructured/template/results.json b/assets/unstructured/template/results.json new file mode 100644 index 0000000..4fb7394 --- /dev/null +++ b/assets/unstructured/template/results.json @@ -0,0 +1,50 @@ +{ + "target": "yourTool", + "targetDisplayedName": "Your Tool Name", + "displayedOrder": 1, + "isEnable": true, + "type": 1, + "ingestTime": 999, + "compressedSize": 999, + "avgIngestMem": 999, + "metrics": [ + { + "metric": 1, + "avgQueryMem": 999, + "queryTimes": [ + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999 + ] + }, + { + "metric": 2, + "avgQueryMem": 999, + "queryTimes": [ + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999, + 999 + ] + } + ] +} diff --git a/assets/unstructured/template/search.sh b/assets/unstructured/template/search.sh new file mode 100755 index 0000000..40dbe4e --- /dev/null +++ b/assets/unstructured/template/search.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# This script takes queries specified in `config.yaml` as command line argument and executes them + +set -e +if [ -z "$1" ]; then + echo "Error: Query argument is missing." + echo "Usage: bash ./search.sh " + exit 1 +fi + +# Your query command goes here diff --git a/assets/unstructured/template/terminate.sh b/assets/unstructured/template/terminate.sh new file mode 100755 index 0000000..e01d39b --- /dev/null +++ b/assets/unstructured/template/terminate.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Nothing to do..." diff --git a/docs/adding-a-tool.md b/docs/adding-a-tool.md new file mode 100644 index 0000000..d3a19ed --- /dev/null +++ b/docs/adding-a-tool.md @@ -0,0 +1,66 @@ +# Add a tool + +🚧 This section is under construction. + +To benchmark a new system, duplicate either [us-assets-template] or [ds-asssets-template] and make +necessary changes. The files in the templates are as follows: + +- **`config.yaml`**: Contains essential benchmarking configurations. + +- **`docker-build.sh`**: Builds the container as per the `Dockerfile` in the same directory. + +- **`docker-run.sh`**: Runs the container, taking the dataset path as an argument. + +- **`Dockerfile`**: Used for building the container, ensuring installation of the required tool to + benchmark and dependencies. + +- **`launch.sh`**: Initializes and starts the tool (e.g., if it functions as a server or + service). + +- **`reset.sh`**: Prepares a clean environment by removing previous data (e.g., dropping + tables); runs after `launch.sh` in `ingest` mode. + +- **`measure-decompressed-size.sh`**: Measures the raw dataset size before ingestion. + Typically unchanged, it takes `datasets_path` from `config.yaml` and uses `du -bc` for size + calculation in bytes. + +- **`ingest.sh`**: Handles data ingestion, with `clp-bench` measuring the total latency of this + script. Avoid adding extra operations. + +- **`measure-compressed-size.sh`**: Measures the compressed data size post-ingestion, usually + via tool-specific methods. + +- **`search.sh`**: Executes queries specified in `config.yaml`. clp-bench supports two + benchmarking modes: + + - **Hot-run mode**: Runs queries for `hot_run_warm_up_times` to warm up the cache, then measures + latency. + - **Cold-run mode**: Clears the cache with `clear-cache.sh` before measuring latency. + +- **`clear-cache.sh`**: Clears the tool's cache, essential for cold runs. + +- **`methodology.md`**: Describes specific benchmarking set up details, including tuning and dataset + preprocessing. + +- **`results.json`**: Contains benchmarking results, which are loaded and displayed in the UI: + + - **`target`**: The ID used by the frontend, should be lowercase. IDs of the same `type` (as + introduced below) must be unique. + - **`targetDisplayedName`**: The name to display in the column on the webpage. + - **`displayedOrder`**: Defines the display order of results; a smaller value places the column + further to the left. + - **`isEnable`**: Indicates if the results should be displayed (default is `true`). If set to + `false`, results won't appear on the webpage. + - **`type`**: Specifies data type (1 for Unstructured, 2 for Dynamically-structured). + - **`ingestTime`**: Total end-to-end time taken to ingest all dataset data. + - **`compressedSize`**: The size of compressed archives. + - **`avgIngestMem`**: The average memory used during ingestion. + - **`metrics`**: An array of query benchmarking results for each metric: + + - **`metric`**: Specifies the type (1 for Hot run, 2 for Cold run). + - **`avgQueryMem`**: The average memory usage during query benchmarking. + - **`queryTimes`**: An array of end-to-end query latencies, ordered to match the sequence of + queries. + +[ds-asssets-template]: assets/dynamically-structured/template +[us-assets-template]: assets/unstructured/template \ No newline at end of file diff --git a/docs/methodology.md b/docs/methodology.md index 0ed0a04..bc0a7d4 100644 --- a/docs/methodology.md +++ b/docs/methodology.md @@ -1,566 +1,155 @@ # Methodology -Below we describe the methodology used to benchmark [CLP] and other tools. + +We benchmark ingestion and query performance for each tool. Since [clp] supports both +**unstructured** and **dynamically-structured** logs, so we separated benchmarks for each. Below, we +describe the datasets, benchmark machines, execution flow, and metrics collected. ## Test datasets + There are two types of logs used in the benchmark: -+ **Unstructured logs** are log events that don't follow a predefined format or structure, making it + +- **Unstructured logs** are log events that don't follow a predefined format or structure, making it difficult to automatically parse or analyze them. Unstructured logs are often just text - interspersed with variable values like the log event's timestamp, log-level, and others such as + interspersed with variable values like the log event's timestamp, log level, and others such as usernames, etc. For example: + ``` 2018-06-05 06:15:29,701 DEBUG org.apache.hadoop.util.Shell: setsid exited with exit code 0 ``` -+ **Semi-structured logs** do have follow a predefined format, making them parseable, but their - schema is not rigid. Semi-structured log events usually have a few consistent fields (e.g., the - event's timestamp and log level) but the rest may appear and disappear across each event. For - example: - - ```json - { - "t": { - "$date": "2023-03-21T23:34:54.576-04:00" - }, - "s": "I", - "c": "NETWORK", - "id": 4915701, - "ctx": "-", - "msg": "Initialized wire specification", - "attr": { - "spec": { - "incomingExternalClient": { - "minWireVersion": 0, - "maxWireVersion": 17 - }, - "incomingInternalClient": { - "minWireVersion": 0, - "maxWireVersion": 17 - }, - "outgoing": { - "minWireVersion": 6, - "maxWireVersion": 17 - }, - "isInternalClient": true - } - } - } +- **Dynamically-structured logs** do have follow a predefined format, making them parseable, but + their schema is not rigid. Dynamically-structured log events usually have a few consistent + fields (e.g., the vent's timestamp and log level) but the rest may appear and disappear across + each event. For example, the `isInternalClient` and `timeoutMillis` fields are dynamic. + + ```json lines + {"t":"2023-03-21T23:34:54.576-04:00","s":"I","msg":"Initialized wire specification","isInternalClient":true} + {"t":"2023-03-21T23:35:12.123-04:00","s":"W","msg":"Connection timeout","timeoutMillis":5000} ``` The unstructured log dataset, hadoop-25GB, contains log events generated by Hadoop when running workloads from the HiBench Benchmark Suite on three Hadoop clusters, each containing 48 data nodes. -The dataset is a 258 GiB subset of a larger [14 TiB][hadoop-14TB] dataset (specifically, workers -1-3). +The dataset is a 258 GiB subset of a larger [14 TiB][hadoop-14tb] dataset which contains 945,729,428 +log events (specifically, workers 1-3). -The semi-structured log dataset, [mongodb], contains log events generated by MongoDB when running -YCSB workloads A-E repeatedly. The dataset is 64.8 GiB and contains 186,287,600 log events. +The dynamically-structured log dataset, [mongodb], contains log events generated by MongoDB when +running YCSB workloads A-E repeatedly. The dataset is 64.8 GiB and contains 186,287,600 log events. + +## Workflow + +clp-bench divides the benchmarking into two parts: **ingest** and **query benchmarking**. The +workflow for ingesting data is: + +```mermaid +flowchart TD + Launch --> |Clear any archives
and temporary files
from previous runs.| Reset + Reset -->|Measure raw data size.
Set start timestamp.| Ingest + Ingest -->|Measure compressed data size.
Set end timestamp.| Terminate +``` + +The workflow for query benchmarking is: + +```mermaid +flowchart TD + A[Launch] -->|Hot run| B[Warmup Cache] + A -->|Cold run| C[Clear Cache] + B -->|Start polling system metric.
Set start timestamp.| D[Execute Query] + C -->|Start polling system metric.
Set start timestamp.| D + D -->|Set end timestamp.| Terminate +``` ## Benchmark machines -Where we report benchmark results, we use a Linux server with Intel Xeon E5-2630v3 processor and -128GB of DDR4 memory. Both the uncompressed and compressed logs are stored on a 7200RPM SATA HDD. + +| Property | Value | +|----------|----------------------| +| OS | Ubuntu-18.04 | +| CPU | Intel Xeon E5-2630v3 | +| Memory | 128GB of DDR4 | +| Storage | 7200RPM SATA | ## Test runtime scenarios -We test two possible runtime scenarios for each tool: -+ **Hot Run** is where we ingest the test dataset and then immediately run the query benchmark. - + TODO: In the future, we'll warm up the system by running the benchmark until the results remain - consistent, and then collect results. -+ **Cold Run** is where we ingest the data, restart the system, and then run the query benchmark. - + TODO: In the future, we plan to implement a more comprehensive approach by clearing the OS' - caches to simulate a completely cold environment. + +We evaluate two runtime scenarios for query benchmarking with each tool: + +- **Hot run**: In this scenario, we run the query multiple times to warm up the cache, then execute + the query again to measure end-to-end latency. + + - _Note_: In the future, we plan to warm up the system by running the benchmark until results + stabilize before collecting final measurements. + +- **Cold run**: In this scenario, we clear the page cache and any internal cache of the tool (if + applicable), then run the query to measure end-to-end latency. ## Metrics collected + ### Ingest time + This measures the time taken to ingest the data. Smaller values are better indicating faster ingestion performance. ### Compressed size + This measures the on-disk size of the data, post-ingestion, in the tool being tested. We measure it -using `du -bc`. Smaller values are better indicating higher compression. +using `du -bc` . Smaller values are better indicating higher compression. ### Average memory usage + This measures average memory usage, separately, for the ingestion and query stages. Smaller values are better indicating lower resource usage. There are two collection methods based on how the tools being tested run: -+ For tools running with multiple microservices (e.g., Loki), we use `docker stats` to poll the + +- For tools running with multiple microservices (e.g., Loki), we use `docker stats` to poll the total memory usage of all related containers, then average the results. -+ For other tools, we use `ps` to poll the `RSS` (resident-set size) field for all related +- For other tools, we use `ps` to poll the `RSS` (resident-set size) field for all related processes, then average the results. ### Query latency + This measures the time taken to completely execute a query. Smaller values are better indicating faster query performance. # Tested tools -The benchmark currently tests the following tools: -+ For unstructured logs: - + [CLP][glt]. Specifically, the `glt` binary. - + [Elasticsearch]. - + [Loki]. - + `grep`. -+ For semi-structured logs: - + [CLP-S][clp-s]. - + [Elasticsearch]. - -# Tool specifics -Each tool requires different setup and configuration steps to run the benchmark. - -## CLP and CLP-S -To begin, download the latest released binary, or clone the most recent [code][CLP] and compile it -locally by following these [instructions][core-build]. Note that the benchmark expects that the -tools are run in Docker containers, so you should follow the previous instructions inside a Docker -container. - -We use the `glt` binary for CLP (unstructured logs) and `clp-s` for CLP-S (semi-structured logs). - -### CLP -For CLP, start by creating a `yaml` configuration file like the example below: -```yaml -system_metric: - enable: True - memory: - ingest_polling_interval: 5 - run_query_benchmark_polling_interval: 5 - -glt: - container_id: xiaochong-clp-oss-jammy-xiaochong - binary_path: /home/xiaochong/develop/clp-bench/tests/clp-unstructured/glt-local-compiled - data_path: /home/xiaochong/develop/clp-bench/tests/clp-unstructured/offline-mode-data - dataset_path: /home/xiaochong/develop/clp-bench/tests/datasets/hadoop-small - queries: - - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' - - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' - - '" INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: Container "' - - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' - - '" to pid 21177 as user "' - - '" 10000 reply: "' - - '" 10 reply: "' - - '" 178.2 MB "' - - '" 1.9 GB "' - - '"job_1528179349176_24837"' - - '"blk_1075089282_1348458"' - - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' - - '" abcde "' -``` - -Note that in this configuration (which also applies for the following benchmark targets): -+ `data_path` specifies where the tool will store ingested data. -+ `dataset_path` refers to the location of data that is ready to be ingested. - -With the `yaml` file configured and the container running, you can execute the following command to -run the benchmarks: -```shell -clp-bench -t GLT -m {mode} -c {path-to-yaml} -``` -Here, `mode` can be set to `hot`, `cold`, or `query-only` (which also applies for the following -benchmark targets). For more details, run `clp-bench --help`. - -No preprocessing is needed for the raw data. During data ingestion, `clp-bench` will execute: -```shell -docker exec {container_id} {binary_path} c {data_path} {dataset_path} -``` - -For query benchmarking, `clp-bench` runs the following command for each query: -```shell -docker exec {container_id} {binary_path} s {data_path} {query} -``` -To verify the results, `clp-bench` appends `| wc -l` to count the matched log lines, ensuring the -tool correctly identifies matches during the query process (which also applies for the following -benchmark targets). - -For memory monitoring, `clp-bench` periodically executes `ps aux` (based on the intervals specified -under `system.memory` in the `yaml` file) within the container, checking the `RSS` field for -processes associated with `binary_path` and `data_path`: -```shell -docker exec {container_id} ps aux -``` -The averages of the memory usage collected during ingestion and query benchmark stages will be -calculated respectively. - -### CLP-S -CLP-S' setup is nearly identical to CLP's. Below is an example of a `yaml` configuration file for -CLP-S: -```yaml -system_metric: - enable: True - memory: - ingest_polling_interval: 5 - run_query_benchmark_polling_interval: 5 - -clp_s: - container_id: xiaochong-clp-oss-jammy-xiaochong - binary_path: /home/xiaochong/develop/clp-bench/tests/clp-json/clp-json-x86_64-v0.1.3/bin/clp-s-local-compiled - data_path: /home/xiaochong/develop/clp-bench/tests/clp-json/offline-mode-data - dataset_path: /home/xiaochong/develop/clp-bench/tests/datasets/mongodb-single - queries: - - 'attr.tickets:*' - - 'id: 22419' - - 'attr.message.msg: log_release* AND attr.message.session_name: connection' - - 'ctx: initandlisten AND (NOT msg: "WiredTigermessage" OR attr.message.msg: log_remove*)' - - 'c: WTWRTLOG AND attr.message.ts_sec > 1679490000' - - 'ctx: FlowControlRefresher AND attr.numTrimmed: 0' -``` -The difference is the query's format. The query for JSON logs uses [KQL]. -Similarly, with the `yaml` file configured and the container running, you can execute the following -command to run the benchmarks: -```shell -clp-bench -t CLPS -m {mode} -c {path-to-yaml} -``` +The tables below list the current set of benchmarked tools, as well as the methodology used for each +tool. -Like CLP, no preprocessing of raw data is required. The commands for data ingestion and query -benchmarking remain the same. Memory monitoring is also performed using `ps aux` and checking the -`RSS` field. - -### grep -*grep* is a command-line tool in Unix/Linux systems used to search for specific patterns within -files or text. We use it as a baseline of unstructured log query benchmark. - -Below is an example of `yaml` file of it: -```yaml -system_metric: - enable: False - -grep: - dataset_path: /home/xiaochong/develop/clp-bench/tests/datasets/hadoop-small - queries: - - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' - - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' - - '" INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: Container "' - - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' - - '" to pid 21177 as user "' - - '" 10000 reply: "' - - '" 10 reply: "' - - '" 178.2 MB "' - - '" 1.9 GB "' - - '"job_1528179349176_24837"' - - '"blk_1075089282_1348458"' - - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' - - '" abcde "' -``` -Since *grep* does not ingest data, so there is no difference between modes, you can randomly choose -one: -```shell -clp-bench -t Grep -m {mode} -c {path-to-yaml} -``` - -For the query benchmark, `clp-bench` executes the following command for each query: -```shell -grep -r {query} {dataset_path} -``` - -### Loki -[Loki] runs with two microservices. Loki itself is working as a backend which ingests the data sent -by the log collector. We use [Promtail] as its log collector. These two are running in two -containers communicated through REST APIs. For query benchmark, we use [LogCLI] to execute queries. - -We haven't integrated launching and ingesting for Loki into `clp-bench` yet, so you may need to -manually launch and ingest data first, then use `clp-bench` to run the query benchmark. - -#### Launch -To run Loki, create a `loki-config.yaml` configuration file as the following (see details -[here][loki-config]): -```yaml -auth_enabled: false - -server: - http_listen_port: 3100 - grpc_listen_port: 9096 - -common: - instance_addr: 127.0.0.1 - path_prefix: /tmp/loki - storage: - filesystem: - chunks_directory: /tmp/loki/chunks - rules_directory: /tmp/loki/rules - replication_factor: 1 - ring: - kvstore: - store: inmemory - -query_range: - results_cache: - cache: - embedded_cache: - enabled: true - max_size_mb: 1000 - -schema_config: - configs: - - from: 1972-10-24 - store: tsdb - object_store: filesystem - schema: v13 - index: - prefix: index_ - period: 24h - -limits_config: - reject_old_samples: false - ingestion_rate_mb: 1000 - -ruler: - alertmanager_url: http://localhost:9093 -``` - -Then launch a container for Loki with the following command (assuming you are currently at the -directory which contains the `yaml` configuration file above): -```shell -docker run \ - --name loki \ - -d \ - -v $(pwd):/mnt/config \ - -p 3100:3100 \ - grafana/loki:3.0.0 \ - -config.file=/mnt/config/loki-config.yaml -``` +## Unstructured Logs -To run Promtail, create a `promtail-config.yaml` file as the following: -```yaml -server: - http_listen_port: 9080 - grpc_listen_port: 0 - -positions: - filename: /tmp/positions.yaml - -clients: - - url: http://loki:3100/loki/api/v1/push - -scrape_configs: -- job_name: system - static_configs: - - targets: - - localhost - labels: - job: benchlogs - __path__: /mnt/datasets/hadoop/worker*/* -``` - -Note that `__path__` should be the pattern of directories which contain the log files. - -Then launch a container for Promtail with the following command (assuming you are currently at the -directory which contains the above mentioned `yaml` configuration file): -```shell -docker run \ - --name promtail \ - -d \ - -v $(pwd):/mnt/config \ - -v /path/to/hadoop-log-datasets:/mnt/datasets/hadoop \ - --link loki \ - grafana/promtail:3.0.0 \ - -config.file=/mnt/config/promtail-config.yaml -``` - -When containers for Loki and Promtail have been launched, run the following command until it prints -`ready`, then Loki will start ingesting data: -```shell -curl -G http://localhost:3100/ready -``` - -#### Ingest -Loki ingests data automatically when connects to Promtail. We do not do any preprocessing for the -dataset. - -We use `docker stats` to get the memory usage of ingesting data periodically (the frequency should -be consistent with the `yaml` configuration file for `clp-bench as mentioned in the next section) -until the ingestion finishes: -```shell -docker stats loki promtail --no-stream -``` - -Since Loki does not have prompt when ingestion finishes, we monitor the current ingested data by the -following command: -```shell -curl -G http://localhost:3100/metrics | grep 'loki_distributor_bytes_received_total' -``` - -When the above command prints the size that equals to the size of the dataset, it means the -ingestion finishes. Then we run the following command to get the time spent for ingesting data: -```shell -curl -G http://localhost:3100/metrics | \ - grep 'loki_request_duration_seconds_sum{method="POST",route="loki_api_v1_push"' -``` - -#### Query Benchmarking -An example of `yaml` configuration file of Loki for `clp-bench` to run the query benchmark is like: -```yaml -system_metric: - enable: True - memory: - ingest_polling_interval: 5 - run_query_benchmark_polling_interval: 5 - -loki: - logcli_binary_path: /home/xiaochong/develop/loki/logcli - job: benchlogs - limit: 2000000 - batch: 1000 - from: '2024-10-08T10:00:00Z' - to: '2024-10-09T10:00:00Z' - interval: 30 - queries: - - '" org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "' - - '" org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "' - - '" INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: Container "' - - '" DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="' - - '" to pid 21177 as user "' - - '" 10000 reply: "' - - '" 10 reply: "' - - '" 178.2 MB "' - - '" 1.9 GB "' - - '"job_1528179349176_24837"' - - '"blk_1075089282_1348458"' - - '"hdfs://master:8200/HiBench/Bayes/temp/worddict"' - - '" abcde "' -``` - -Note that in this configuration: -+ `loki.job` should match the `job` of the `labels` in the `promtail-config.yaml` (see the example - of `promtail-config.yaml`). -+ `limit` should be at least the maximum number of matched log lines of the query. -+ `batch` is maximum number of matched log lines that Loki will send to the client at once. -+ `from` and `to` define the rough wall-clock time range of ingesting data. In this example, it - means that data ingestion happened between `2024-10-08T10:00:00Z` and `2024-10-09T10:00:00Z`. -+ `interval` defines the time range, in minutes, that Loki will use to query log lines ingested - within that period. For example, setting an interval of 30 means the time range between from and - to will be divided into 30-minute slices. Loki will run the query on log lines ingested during - each of these slices. For each query, `clp-bench` instructs Loki to execute the query across all - time slices, ensuring the entire dataset is covered. - -With the `yaml` configuration file for `clp-bench` (which is different from the `yaml` file for Loki -and Promtail containers) and running containers of Loki and Promtail (all data must have been -ingested), we can run the following command to run the query benchmark: -```shell -clp-bench -t GrafanaLoki -m query-only -c {path-to-loki-clp-bench-yaml} -``` - -For each query, `clp-bench` run the following command. Note that the `time_slice_start` and -`time_slice_to` are the start and end timestamps for each time slice, `clp-bench` will iterate all -time slices between `from` and `to` in the configuration to cover the entire dataset. -```shell - \ - query '{ job="{job}" } |~ {query}' \ - --limit={limit} \ - --batch={batch} \ - --from="{time_slice_start}" \ - --to="{time_slice_to}" -``` - -For measuring memory usage during query execution, we employ the same method used for data -ingestion. - -### Elasticsearch -We benchmarked Elasticsearch for both unstructured and semi-structured logs. They are basically the -same, except the query's format and the preprocessing of the semi-structured log dataset. Generally, -we use single-node deployment of Elasticsearch, disabling the security feature of -[xpack][disabling-xpack]. - -For unstructured logs, an example of the `yaml` configuration file is like: -```yaml -system_metric: - enable: True - memory: - ingest_polling_interval: 10 - run_query_benchmark_polling_interval: 1 - -elasticsearch: - container_id: elasticsearch-xiaochong - launch_script_path: /home/assets/start-ela.sh - compress_script_path: /home/assets/compress.py - search_script_path: /home/assets/query.py - terminate_script_path: /home/assets/stop-ela.sh - memory_polling_script_path: /home/assets/poll_mem.py - data_path: /var/lib/elasticsearch - log_path: /var/log/elasticsearch - dataset_path: /home/datasets/worker*/worker*/*log* - queries: - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " org.apache.hadoop.hdfs.server.common.Storage: Analyzing storage directories for bpid "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " org.apache.hadoop.hdfs.server.datanode.DataNode: DataTransfer, at "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " INFO org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerImpl: Container "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " DEBUG org.apache.hadoop.mapred.ShuffleHandler: verifying request. enc_str="}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " to pid 21177 as user "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 10000 reply: "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 10 reply: "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 178.2 MB "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " 1.9 GB "}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "job_1528179349176_24837"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "blk_1075089282_1348458"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": "hdfs://master:8200/HiBench/Bayes/temp/worddict"}}}}, "size": 10000}' - - '{"query": {"bool": {"must": {"match_phrase": {"log_line": " abcde "}}}}, "size": 10000}' -``` - -For semi-structured logs, an example of the `yaml` configuration file is like: -```yaml -system_metric: - enable: True - memory: - ingest_polling_interval: 10 - run_query_benchmark_polling_interval: 1 - -elasticsearch: - container_id: elasticsearch-semi-xiaochong - launch_script_path: /home/assets/start-ela.sh - compress_script_path: /home/assets/compress.py - search_script_path: /home/assets/query.py - terminate_script_path: /home/assets/stop-ela.sh - memory_polling_script_path: /home/assets/poll_mem.py - data_path: /var/lib/elasticsearch - log_path: /var/log/elasticsearch - dataset_path: /home/datasets/mongod.log - queries: - - '{"query": {"exists": {"field": "attr.tickets"}}, "size": 10000}' - - '{"query": {"term": {"id": 22419}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"wildcard": {"attr.message.msg": "log_release*"}}, {"match": {"attr.message.session_name": "connection"}}]}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"ctx": "initandlisten"}}], "should": [{"wildcard": {"attr.message.msg": "log_remove*"}}, {"bool": {"must_not": [{"match_phrase": {"msg": "WiredTiger message"}}]}}], "minimum_should_match": 1}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"c": "WTWRTLOG"}}, {"range": {"attr.message.ts_sec": {"gt": 1679490000}}}]}}, "size": 10000}' - - '{"query": {"bool": {"must": [{"match": {"ctx": "FlowControlRefresher"}}, {"match": {"attr.numTrimmed": 0}}]}}, "size": 10000}' -``` - -Note that there are some scripts used by `clp-bench` can be found under -`clp-bench/assets/elasticsearch-unstructured` (for unstructured logs) and -`clp-bench/assets/elasticsearch` (for semi-structured logs). There are also `Dockerfile` -can be found under these directories, which are used to build the containers with `docker_build.sh`. -Once the containers are built, use `docker_run.sh` to launch them. The script requires two -arguments: the first is the absolute path of the dataset on the host, and the second is the absolute -path where the ingested data will be stored on the host. - -With the `yaml` file and the running container corresponding to the log dataset type, we can run the -following command to benchmark Elasticsearch: -```shell -# For unstructured log dataset -clp-bench -t ElasticsearchUnstructured -m {mode} -c {path-to-yaml} -# For semi-structured log dataset -clp-bench -t Elasticsearch -m {mode} -c {path-to-yaml} -``` +| Tool | Methodology | +|--------------------------------|-----------------------------------------------| +| [clp][glt] | 📐[Methodology][glt-methodology] | +| [Elasticsearch][elasticsearch] | 📐[Methodology][elasticsearch-us-methodology] | +| `grep` | 📐[Methodology][grep-methodology] | +| [Loki][loki] | 📐[Methodology][loki-methodology] | +| [Splunk][splunk] | 📐[Methodology][splunk-methodology] | -For data ingestion, no preprocessing is needed for unstructured log datasets. However, JSON logs -generated by MongoDB require some adjustments to be searchable by Elasticsearch. For details, refer -to the `traverse_data` function in `clp-bench/assets/elasticsearch/compress.py`. The -general approach involves reorganizing certain fields by moving them to outer or inner objects to -ensure the queries function correctly. For both types of dataset, `clp-bench` uses `streaming_bulk` -from `elasticsearch.helpers` to ingest data (refer to either -`clp-bench/assets/elasticsearch-unstructured/compress.py` or -`clp-bench/assets/elasticsearch/compress.py`). +## Dynamically-Structured Logs -For query benchmarking, `clp-bench` also uses functionality from `elasticsearch` python package to -execute queries. For details, refer to `execute_query_without_cache` functions in -`clp-bench/assets/elasticsearch-unstructured/query.py` and -`clp-bench/assets/elasticsearch/query.py`. +| Tool | Methodology | +|--------------------------------|-----------------------------------------------| +| [ClickHouse][clickhouse] | 📐[Methodology][clickhouse-methodology] | +| [clp-s][clp-s] | 📐[Methodology][clp-s-methodology] | +| [Elasticsearch][elasticsearch] | 📐[Methodology][elasticsearch-ds-methodology] | +| [MongoDB][mongodb] | 📐[Methodology][mongodb-methodology] | -For memory monitoring, similar to CLP and CLP-S, `clp-bench` uses `ps aux` and checks the `RSS` -field. -[CLP]: https://github.com/y-scope/clp +[clickhouse]: https://clickhouse.com/ +[clickhouse-methodology]: ../assets/dynamically-structured/clickhouse/methodology.md +[clp]: https://github.com/y-scope/clp [clp-s]: https://docs.yscope.com/clp/main/user-guide/core-clp-s.html -[core-build]: https://docs.yscope.com/clp/main/dev-guide/components-core/index.html +[clp-s-methodology]: ../assets/dynamically-structured/clp-s/methodology.md +[elasticsearch]: https://www.elastic.co/downloads/elasticsearch +[elasticsearch-ds-methodology]: ../assets/dynamically-structured/elasticsearch/methodology.md +[elasticsearch-us-methodology]: ../assets/unstructured/elasticsearch/methodology.md [glt]: https://docs.yscope.com/clp/main/user-guide/core-unstructured/glt.html -[disabling-xpack]: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html -[Elasticsearch]: https://www.elastic.co/downloads/elasticsearch -[hadoop-14TB]: https://zenodo.org/records/7114847 -[KQL]: https://docs.yscope.com/clp/main/user-guide/reference-json-search-syntax.html -[LogCLI]: https://grafana.com/docs/loki/latest/query/logcli/ -[loki-config]: https://grafana.com/docs/loki/latest/configure/ -[Loki]: https://grafana.com/oss/loki/ +[glt-methodology]: ../assets/unstructured/glt/methodology.md +[grep-methodology]: ../assets/unstructured/grep/methodology.md +[hadoop-14tb]: https://zenodo.org/records/7114847 +[loki]: https://grafana.com/oss/loki/ +[loki-methodology]: ../assets/unstructured/loki🚧/methodology.md +[mongodb]: https://www.mongodb.com/ +[mongodb-methodology]: ../assets/dynamically-structured/mongodb/methodology.md [mongodb]: https://zenodo.org/records/11075361 -[Promtail]: https://grafana.com/docs/loki/latest/send-data/promtail/ +[splunk]: https://www.splunk.com/ +[splunk-methodology]: ../assets/unstructured/splunk/methodology.md diff --git a/src/clp_bench/__init__.py b/src/clp_bench/__init__.py index aacd659..02b6442 100644 --- a/src/clp_bench/__init__.py +++ b/src/clp_bench/__init__.py @@ -1,150 +1,125 @@ import argparse -import importlib import logging import traceback -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase -from .version import VERSION, VERSION_SHORT +from .executor import BenchmarkingMode, BenchmarkingSystemMetric, ClpBenchExecutor +from .version import VERSION -# Setup logging -# Create logger logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -# Setup console logging logging_console_handler = logging.StreamHandler() logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") logging_console_handler.setFormatter(logging_formatter) logger.addHandler(logging_console_handler) -def load_executor_class(target_tool_name: str, config_path: str) -> CPTExecutorBase: - logger.info("Loading executor implementation for: " + target_tool_name) - module = importlib.import_module("clp_bench." + target_tool_name.lower() + "_executor") - cls = getattr(module, "CPTExecutor" + target_tool_name) - return cls(config_path) +def load_benchmarking_assets(assets_path: str) -> ClpBenchExecutor: + """This function loads the benchmarking assets of the tool to benchmark, including container + configurations and benchmarking scripts + :param assets_path: A string of the path to the assets + :return: An executor instance + """ + + logger.info(f"Loading benchmarking assets from: {assets_path}") + return ClpBenchExecutor(assets_path) -def hot_run_benchmark(executor: CPTExecutorBase): - logger.info("Running benchmark in hot-run mode") - try: - executor.start_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.HOT_RUN_MODE - ) - executor.deploy(BenchmarkingMode.HOT_RUN_MODE) - executor.launch(BenchmarkingMode.HOT_RUN_MODE) - executor.ingest(BenchmarkingMode.HOT_RUN_MODE) - executor.run_query_benchmark(BenchmarkingMode.HOT_RUN_MODE) - except Exception as e: - traceback.print_exc() - logger.error(f"Failed to run benchmark in hot-run mode: {e}") - finally: - executor.stop_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.HOT_RUN_MODE - ) - try: - executor.terminate(BenchmarkingMode.HOT_RUN_MODE) - except Exception as e: - logger.error(f"Failed to finish benchmark in hot-run mode: {e}") +def ingest(clp_bench_executor: ClpBenchExecutor): + """This function is invoked when work mode is "ingest" -def cold_run_benchmark(executor: CPTExecutorBase): - logger.info("Running benchmarking in cold-run mode") + :param clp_bench_executor: The executor instance + """ + + logger.info("Ingesting data") try: - executor.start_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.COLD_RUN_MODE + clp_bench_executor.start_polling_system_metric( + BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.INGEST_MODE ) - executor.deploy(BenchmarkingMode.COLD_RUN_MODE) - executor.launch(BenchmarkingMode.COLD_RUN_MODE) - executor.ingest(BenchmarkingMode.COLD_RUN_MODE) - executor.mid_terminate(BenchmarkingMode.COLD_RUN_MODE) - executor.launch(BenchmarkingMode.COLD_RUN_MODE) - executor.run_query_benchmark(BenchmarkingMode.COLD_RUN_MODE) + clp_bench_executor.launch(BenchmarkingMode.INGEST_MODE) + clp_bench_executor.ingest(BenchmarkingMode.INGEST_MODE) except Exception as e: - logger.error(f"Failed to run benchmark in cold-run mode: {e}") + logger.error(f"Failed to ingest in {BenchmarkingMode.INGEST_MODE.value} mode : {e}") finally: - executor.stop_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.COLD_RUN_MODE + clp_bench_executor.stop_polling_system_metric( + BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.INGEST_MODE ) try: - executor.terminate(BenchmarkingMode.COLD_RUN_MODE) + clp_bench_executor.terminate() except Exception as e: - logger.error(f"Failed to finish benchmark in cold-run mode: {e}") + logger.error( + f"Failed to finish benchmark in {BenchmarkingMode.INGEST_MODE.value} mode: {e}" + ) + +def run_query_benchmark( + clp_bench_executor: ClpBenchExecutor, mode: BenchmarkingMode = BenchmarkingMode.HOT_RUN_MODE +): + """This functions runs the query benchmark, where the queries are from the yaml file in assets -def query_only_run_benchmark(executor: CPTExecutorBase): - logger.info("Running benchmarking in query-only-run mode") + :param clp_bench_executor: The executor instance + :param mode: The work mode (could be one of "cold" or "hot") + """ + + logger.info(f"Running benchmarking in {mode.value} mode") try: - executor.start_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.QUERY_ONLY_RUN_MODE - ) - # Query-only run mode no need to deploy, it assumes just finished a hot-run or cold-run benchmarking. - executor.launch(BenchmarkingMode.QUERY_ONLY_RUN_MODE) - executor.run_query_benchmark(BenchmarkingMode.QUERY_ONLY_RUN_MODE) + clp_bench_executor.start_polling_system_metric(BenchmarkingSystemMetric.MEMORY, mode) + # Note that no deployment here, it assumes finished ingestion. + clp_bench_executor.launch(mode) + clp_bench_executor.run_query_benchmark(mode) except Exception as e: - logger.error(f"Failed to run benchmark in query-only-run mode: {e}") + logger.error(f"Failed to run benchmark in {mode.value} mode: {e}") finally: - executor.stop_polling_system_metric( - BenchmarkingSystemMetric.MEMORY, BenchmarkingMode.QUERY_ONLY_RUN_MODE - ) + clp_bench_executor.stop_polling_system_metric(BenchmarkingSystemMetric.MEMORY, mode) + try: + clp_bench_executor.terminate() + except Exception as e: + logger.error(f"Failed to finish benchmark in {mode.value} mode: {e}") def main(): + description = "CLP Bench--An out-of-the-box benchmarking framework." # Command line arguments parsing - parser = argparse.ArgumentParser( - description="CLP Bench--An out-of-the-box benchmarking framework." - ) - parser.add_argument( - "-t", - "--target", - type=str, - # The first line is for semi-structured; the second line is for unstructured - choices=[ - "CLPJson", - "CLPS", - "Elasticsearch", - "GrafanaLoki", - "CLPG", - "GLT", - "Grep", - "ElasticsearchUnstructured", - ], - required=True, - help="The target tool you want to benchmark", - ) + parser = argparse.ArgumentParser(description=description) parser.add_argument( - "-c", "--config", type=str, default="./config.yaml", help="The yaml config file location" + "-a", "--asset", type=str, required=True, help="The benchmarking asset location" ) parser.add_argument( "-m", "--mode", type=str, - choices=["all", "hot", "cold", "query-only"], - default="all", + choices=["cold", "hot", "ingest"], + required=True, help="The benchmarking mode", ) + parser.add_argument("-d", "--debug", action="store_true") + parser.add_argument("-v", "--version", action="version", version=f"{VERSION}") args = parser.parse_args() - logger.info(f"Target tool is {args.target}") - logger.info(f"The config file location: {args.config}") + logger.info(f"The benchmarking asset location: {args.asset}") logger.info(f"The benchmarking mode: {args.mode}") - # Load cooresponding implementation for executor's SPI + if args.debug: + logger.info("Enable DEBUG mode") + logger.setLevel(logging.DEBUG) + + # Load corresponding implementation for executor's SPI try: - executor = load_executor_class(args.target, args.config) + clp_bench_executor = load_benchmarking_assets(args.asset) except Exception as e: traceback.print_exc() logger.error(e) return - # Hot run mode with warm cache benchmarking - if "all" == args.mode or "hot" == args.mode: - hot_run_benchmark(executor) - # Cold run mode with cold cache benchmarking - if "all" == args.mode or "cold" == args.mode: - cold_run_benchmark(executor) + if "cold" == args.mode: + run_query_benchmark(clp_bench_executor, BenchmarkingMode.COLD_RUN_MODE) + + # Hot run mode with warm cache benchmarking + if "hot" == args.mode: + run_query_benchmark(clp_bench_executor, BenchmarkingMode.HOT_RUN_MODE) - # Query only run mode, assuming just finished a hot run or cold run - if "query-only" == args.mode: - query_only_run_benchmark(executor) + # Only ingest the data + if "ingest" == args.mode: + ingest(clp_bench_executor) - executor.visualize() + clp_bench_executor.visualize() diff --git a/src/clp_bench/clpg_executor.py b/src/clp_bench/clpg_executor.py deleted file mode 100644 index 664cd96..0000000 --- a/src/clp_bench/clpg_executor.py +++ /dev/null @@ -1,118 +0,0 @@ -import logging -import subprocess -import time - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorCLPG(CPTExecutorBase): - """ - A service provider for clp, which is a binary; clg is used for searching. - """ - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying CLP and CLG") - container_id = self.config["clpg"]["container_id"] - logger.info(f"clp docker container ID: {container_id}") - clp_binary_path = self.config["clpg"]["clp_binary_path"] - logger.info(f"clp binary location: {clp_binary_path}") - clg_binary_path = self.config["clpg"]["clg_binary_path"] - logger.info(f"clg binary location: {clg_binary_path}") - data_path = self.config["clpg"]["data_path"] - logger.info(f"clp data location: {data_path}") - dataset_path = self.config["clpg"]["dataset_path"] - logger.info(f"clp dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, clp_binary_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for clp") - container_id = self.config["clpg"]["container_id"] - clp_binary_path = self.config["clpg"]["clp_binary_path"] - data_path = self.config["clpg"]["data_path"] - dataset_path = self.config["clpg"]["dataset_path"] - try: - result = subprocess.run( - ["du", dataset_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - decompressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].decompressed_size = f"{decompressed_size_mb:.2f}MB" - start_ts = time.perf_counter_ns() - subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - f"{clp_binary_path} c {data_path} {dataset_path}", - ], - check=True, - ) - end_ts = time.perf_counter_ns() - elapsed_time = (end_ts - start_ts) / 1e9 - self.benchmarking_reseults[mode].ingest_e2e_latency = f"{elapsed_time:.9f}s" - # FIXME: this is inconsistent with clp-s genereated archives permission - subprocess.run( - ["sudo", "find", data_path, "-exec", "chmod", "o+r+x", "{}", ";"], check=True - ) - result = subprocess.run( - ["du", data_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - compressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].compressed_size = f"{compressed_size_mb:.2f}MB" - self.benchmarking_reseults[mode].ratio = f"{decompressed_size_mb / compressed_size_mb}x" - except subprocess.CalledProcessError as e: - raise Exception(f"clp failed to compress data: {e}") - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for clp") - container_id = self.config["clpg"]["container_id"] - clg_binary_path = self.config["clpg"]["clg_binary_path"] - data_path = self.config["clpg"]["data_path"] - queries = self.config["clpg"]["queries"] - try: - for query in queries: - command = f"docker exec {container_id} {clg_binary_path} {data_path} {query}" - self._execute_query(mode, command) - except subprocess.CalledProcessError as e: - raise Exception(f"clp failed to finish the query benchmarking: {e}") - pass - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching clp") - pass - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating clp") - pass - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - container_id = self.config["clpg"]["container_id"] - try: - result = subprocess.run( - ["docker", "stats", container_id, "--no-stream"], stdout=subprocess.PIPE, check=True - ) - output = result.stdout.decode("utf-8").strip().split("\n") - for line in output: - if container_id in line: - return self._get_mem_usage_from_docker_stats(line) - except subprocess.CalledProcessError: - raise Exception("clp failed to get mem usage info") diff --git a/src/clp_bench/clpjson_executor.py b/src/clp_bench/clpjson_executor.py deleted file mode 100644 index 3376c2b..0000000 --- a/src/clp_bench/clpjson_executor.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging -import re -import subprocess -import time - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorCLPJson(CPTExecutorBase): - """ - A service provider for CLP. - """ - - def __init__(self, config_path: str) -> None: - super().__init__(config_path) - # We read memory info directly from elasticsearch's API, there is no need to use baseline - for mode in BenchmarkingMode: - self.benchmarking_reseults[mode].system_metric_results[ - BenchmarkingSystemMetric.MEMORY - ].result_baseline = -1 - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying CLP") - container_id = self.config["clp_json"]["container_id"] - logger.info(f"clp-json docker container ID: {container_id}") - launch_script_path = self.config["clp_json"]["launch_script_path"] - logger.info(f"clp-json launch script location: {launch_script_path}") - compress_script_path = self.config["clp_json"]["compress_script_path"] - logger.info(f"clp-json compress script location: {compress_script_path}") - serach_script_path = self.config["clp_json"]["search_script_path"] - logger.info(f"clp-json search script location: {serach_script_path}") - terminate_script_path = self.config["clp_json"]["terminate_script_path"] - logger.info(f"clp-json terminate script location: {terminate_script_path}") - data_path = self.config["clp_json"]["data_path"] - logger.info(f"clp-json data location: {data_path}") - log_path = self.config["clp_json"]["log_path"] - logger.info(f"clp-json log location: {log_path}") - dataset_path = self.config["clp_json"]["dataset_path"] - logger.info(f"clp-json dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, launch_script_path) - self._check_file_in_docker(container_id, compress_script_path) - self._check_file_in_docker(container_id, serach_script_path) - self._check_file_in_docker(container_id, terminate_script_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker( - container_id, log_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for CLP") - container_id = self.config["clp_json"]["container_id"] - compress_script_path = self.config["clp_json"]["compress_script_path"] - dataset_path = self.config["clp_json"]["dataset_path"] - try: - start_ts = time.perf_counter_ns() - command = f"docker exec {container_id} {compress_script_path} --timestamp-key 't.$date' {dataset_path}" - result = subprocess.run( - command, stderr=subprocess.PIPE, shell=True, check=True, text=True - ) - end_ts = time.perf_counter_ns() - elapsed_time = (end_ts - start_ts) / 1e9 - logger.info( - f"clp-json compressed data in {dataset_path} successfully in {elapsed_time:.9f} seconds" - ) - self.benchmarking_reseults[mode].ingest_e2e_latency = f"{elapsed_time:.9f}s" - output = result.stderr - match = re.search(r"Compressed (\S+).*?into (\S+).*?\((\d+\.\d+x)\)", output) - if match: - self.benchmarking_reseults[mode].decompressed_size = match.group(1) - self.benchmarking_reseults[mode].compressed_size = match.group(2) - self.benchmarking_reseults[mode].ratio = match.group(3) - logger.info("Ingest metrics collected") - else: - logger.error("Cannot get ingest metrics") - except subprocess.CalledProcessError as e: - raise Exception(f"clp-json failed to compress data: {e}") - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for CLP") - container_id = self.config["clp_json"]["container_id"] - search_script_path = self.config["clp_json"]["search_script_path"] - queries = self.config["clp_json"]["queries"] - for query in queries: - command = f"docker exec {container_id} {search_script_path} '{query}'" - self._execute_query(mode, command) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching CLP") - try: - container_id = self.config["clp_json"]["container_id"] - launch_script_path = self.config["clp_json"]["launch_script_path"] - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"{launch_script_path}"], check=True - ) - logger.info(f"clp-json launched successfully in container {container_id}") - except subprocess.CalledProcessError as e: - raise Exception(f"clp-json failed to launch: {e}") - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating CLP") - container_id = self.config["clp_json"]["container_id"] - terminate_script_path = self.config["clp_json"]["terminate_script_path"] - try: - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"{terminate_script_path}"], - check=True, - ) - except subprocess.CalledProcessError as e: - raise Exception(f"clp-json failed to terminate: {e}") - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - container_id = self.config["clp_json"]["container_id"] - while True: - result = subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - 'docker stats $(docker ps --format "{{.Names}}" | grep "^clp-") --no-stream', - ], - stdout=subprocess.PIPE, - ) - output = result.stdout.decode("utf-8").strip().split("\n") - if len(output) > 1: - break - else: - logger.info("Cannot get output of docker stats, try again") - metric_sample = 0 - for line in output[1:]: - metric_sample += self._get_mem_usage_from_docker_stats(line) - return metric_sample diff --git a/src/clp_bench/clps_executor.py b/src/clp_bench/clps_executor.py deleted file mode 100644 index d3a4ec1..0000000 --- a/src/clp_bench/clps_executor.py +++ /dev/null @@ -1,113 +0,0 @@ -import logging -import subprocess -import time - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorCLPS(CPTExecutorBase): - """ - A service provider for clp-s, which is a binary. - """ - - def __init__(self, config_path: str) -> None: - super().__init__(config_path) - # We read memory info directly from elasticsearch's API, there is no need to use baseline - for mode in BenchmarkingMode: - self.benchmarking_reseults[mode].system_metric_results[ - BenchmarkingSystemMetric.MEMORY - ].result_baseline = -1 - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying CLP-S") - container_id = self.config["clp_s"]["container_id"] - logger.info(f"clp-s docker container ID: {container_id}") - binary_path = self.config["clp_s"]["binary_path"] - logger.info(f"clp-s binary location: {binary_path}") - data_path = self.config["clp_s"]["data_path"] - logger.info(f"clp-s data location: {data_path}") - dataset_path = self.config["clp_s"]["dataset_path"] - logger.info(f"clp-s dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, binary_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for clp-s") - container_id = self.config["clp_s"]["container_id"] - binary_path = self.config["clp_s"]["binary_path"] - data_path = self.config["clp_s"]["data_path"] - dataset_path = self.config["clp_s"]["dataset_path"] - try: - result = subprocess.run( - ["du", dataset_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - decompressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].decompressed_size = f"{decompressed_size_mb:.2f}MB" - start_ts = time.perf_counter_ns() - command = f"docker exec {container_id} {binary_path} c --timestamp-key 't.$date' --target-encoded-size 268435456 {data_path} {dataset_path}" - subprocess.run( - command, - shell=True, - check=True, - ) - end_ts = time.perf_counter_ns() - elapsed_time = (end_ts - start_ts) / 1e9 - self.benchmarking_reseults[mode].ingest_e2e_latency = f"{elapsed_time:.9f}s" - result = subprocess.run( - ["du", data_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - compressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].compressed_size = f"{compressed_size_mb:.2f}MB" - self.benchmarking_reseults[mode].ratio = f"{decompressed_size_mb / compressed_size_mb}x" - except subprocess.CalledProcessError as e: - raise Exception(f"clp-s failed to compress data: {e}") - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for clp-s") - container_id = self.config["clp_s"]["container_id"] - binary_path = self.config["clp_s"]["binary_path"] - data_path = self.config["clp_s"]["data_path"] - queries = self.config["clp_s"]["queries"] - for query in queries: - command = f"docker exec {container_id} {binary_path} s {data_path} '{query}'" - self._execute_query(mode, command) - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching clp-s") - pass - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating clp-s") - pass - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - binary_path = self.config["clp_s"]["binary_path"] - data_path = self.config["clp_s"]["data_path"] - container_id = self.config["clp_s"]["container_id"] - try: - command = f"docker exec {container_id} ps aux" - result = subprocess.run(command, stdout=subprocess.PIPE, shell=True, check=True) - output = result.stdout.decode("utf-8").strip().split("\n") - for line in output: - if binary_path in line and data_path in line: - return int(line.strip().split()[5]) - return 0 - except subprocess.CalledProcessError: - raise Exception("clp-s failed to get mem usage info") diff --git a/src/clp_bench/elasticsearch_executor.py b/src/clp_bench/elasticsearch_executor.py deleted file mode 100644 index 6977060..0000000 --- a/src/clp_bench/elasticsearch_executor.py +++ /dev/null @@ -1,190 +0,0 @@ -import logging -import re -import subprocess - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorElasticsearch(CPTExecutorBase): - """ - A service provider for Elasticsearch. - """ - - def __init__(self, config_path: str) -> None: - super().__init__(config_path) - # We read memory info directly from elasticsearch's API, there is no need to use baseline - for mode in BenchmarkingMode: - self.benchmarking_reseults[mode].system_metric_results[ - BenchmarkingSystemMetric.MEMORY - ].result_baseline = -1 - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - logger.info(f"Elasticsearch docker container ID: {container_id}") - launch_script_path = self.config["elasticsearch"]["launch_script_path"] - logger.info(f"Elasticsearch launch script location: {launch_script_path}") - compress_script_path = self.config["elasticsearch"]["compress_script_path"] - logger.info(f"Elasticsearch compress script location: {compress_script_path}") - serach_script_path = self.config["elasticsearch"]["search_script_path"] - logger.info(f"Elasticsearch search script location: {serach_script_path}") - terminate_script_path = self.config["elasticsearch"]["terminate_script_path"] - logger.info(f"Elasticsearch terminate script location: {terminate_script_path}") - data_path = self.config["elasticsearch"]["data_path"] - logger.info(f"Elasticsearch data location: {data_path}") - log_path = self.config["elasticsearch"]["log_path"] - logger.info(f"Elasticsearch log location: {log_path}") - dataset_path = self.config["elasticsearch"]["dataset_path"] - logger.info(f"Elasticsearch dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, launch_script_path) - self._check_file_in_docker(container_id, compress_script_path) - self._check_file_in_docker(container_id, serach_script_path) - self._check_file_in_docker(container_id, terminate_script_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker( - container_id, log_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - compress_script_path = self.config["elasticsearch"]["compress_script_path"] - dataset_path = self.config["elasticsearch"]["dataset_path"] - try: - result = subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - f"python3 {compress_script_path} {dataset_path}", - ], - stderr=subprocess.PIPE, - check=True, - text=True, - ) - output = result.stderr - decompressed_size_match = re.search(r"Original size for \S+ is (\d+)", output) - compressed_size_match = re.search(r"Compressed size for \S+ is (\d+)", output) - ratio_match = re.search(r"Compression ratio for \S+ is (\d+\.\d+)", output) - ingest_e2e_match = re.search(r"Ingestion time for \S+ is (\d+\.\d+) s", output) - if decompressed_size_match: - self.benchmarking_reseults[mode].decompressed_size = ( - f"{int(decompressed_size_match.group(1)) / 1024 / 1024}MB" - ) - logger.info( - f"File size before compression: {self.benchmarking_reseults[mode].decompressed_size}" - ) - else: - logger.error("Cannot get decompressed metric") - if compressed_size_match: - self.benchmarking_reseults[mode].compressed_size = ( - f"{int(compressed_size_match.group(1)) / 1024 / 1024}MB" - ) - logger.info( - f"File size after compression: {self.benchmarking_reseults[mode].compressed_size}" - ) - else: - logger.error("Cannot get compressed metric") - if ratio_match: - self.benchmarking_reseults[mode].ratio = f"{ratio_match.group(1)}x" - logger.info(f"Compression ratio: {self.benchmarking_reseults[mode].ratio}") - else: - logger.error("Cannot get compression ratio metric") - if ingest_e2e_match: - self.benchmarking_reseults[mode].ingest_e2e_latency = ( - f"{ingest_e2e_match.group(1)}s" - ) - logger.info( - f"Elasticsearch compressed data in {dataset_path} successfully in {ingest_e2e_match.group(1)} seconds" - ) - else: - logger.error("Cannot get ingest end-to-end latency metric") - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to compress data: {e}") - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - search_script_path = self.config["elasticsearch"]["search_script_path"] - queries = self.config["elasticsearch"]["queries"] - for query in queries: - command = f"docker exec {container_id} python3 {search_script_path} '{query}'" - self._execute_query(mode, command) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching Elasticsearch") - try: - container_id = self.config["elasticsearch"]["container_id"] - launch_script_path = self.config["elasticsearch"]["launch_script_path"] - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"bash {launch_script_path}"], - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT, - check=True, - ) - logger.info(f"Elasticsearch launched successfully in container {container_id}") - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to launch: {e}") - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating Elasticsearch") - try: - container_id = self.config["elasticsearch"]["container_id"] - terminate_script_path = self.config["elasticsearch"]["terminate_script_path"] - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"bash {terminate_script_path}"], - check=True, - ) - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to terminate: {e}") - - # def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - # if BenchmarkingSystemMetric.MEMORY == metric: - # container_id = self.config['elasticsearch']['container_id'] - # memory_polling_script_path = self.config['elasticsearch']['memory_polling_script_path'] - # metric_sample = -1 - # # In poll_mem.py, when there is an exception, it will print -1 - # while (-1 == metric_sample): - # result = subprocess.run( - # ['docker', 'exec', container_id, 'python3', memory_polling_script_path], - # stdout=subprocess.PIPE, - # check=True - # ) - # metric_sample = int(result.stdout.decode('utf-8').strip()) / 1024 - # if -1 == metric_sample: - # time.sleep(1) - # elif BenchmarkingSystemMetric.CPU == metric: - # metric_sample = 0 - # # TODO - # else: - # raise Exception(f"Unknow metric: {metric.value[0]}") - # return metric_sample - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - container_id = self.config["elasticsearch"]["container_id"] - try: - command = f"docker exec {container_id} ps aux" - result = subprocess.run(command, stdout=subprocess.PIPE, shell=True, check=True) - output = result.stdout.decode("utf-8").strip().split("\n") - metric = 0 - for line in output: - if "/usr/share/elasticsearch" in line: - metric += int(line.strip().split()[5]) - return metric - except subprocess.CalledProcessError: - raise Exception("Elasticsearch failed to get mem usage info") diff --git a/src/clp_bench/elasticsearchunstructured_executor.py b/src/clp_bench/elasticsearchunstructured_executor.py deleted file mode 100644 index 37b4fbf..0000000 --- a/src/clp_bench/elasticsearchunstructured_executor.py +++ /dev/null @@ -1,190 +0,0 @@ -import logging -import re -import subprocess - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorElasticsearchUnstructured(CPTExecutorBase): - """ - A service provider for elasticsearch (unstructured). - """ - - def __init__(self, config_path: str) -> None: - super().__init__(config_path) - # We read memory info directly from elasticsearch's API, there is no need to use baseline - for mode in BenchmarkingMode: - self.benchmarking_reseults[mode].system_metric_results[ - BenchmarkingSystemMetric.MEMORY - ].result_baseline = -1 - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - logger.info(f"Elasticsearch docker container ID: {container_id}") - launch_script_path = self.config["elasticsearch"]["launch_script_path"] - logger.info(f"Elasticsearch launch script location: {launch_script_path}") - compress_script_path = self.config["elasticsearch"]["compress_script_path"] - logger.info(f"Elasticsearch compress script location: {compress_script_path}") - serach_script_path = self.config["elasticsearch"]["search_script_path"] - logger.info(f"Elasticsearch search script location: {serach_script_path}") - terminate_script_path = self.config["elasticsearch"]["terminate_script_path"] - logger.info(f"Elasticsearch terminate script location: {terminate_script_path}") - data_path = self.config["elasticsearch"]["data_path"] - logger.info(f"Elasticsearch data location: {data_path}") - log_path = self.config["elasticsearch"]["log_path"] - logger.info(f"Elasticsearch log location: {log_path}") - dataset_path = self.config["elasticsearch"]["dataset_path"] - logger.info(f"Elasticsearch dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, launch_script_path) - self._check_file_in_docker(container_id, compress_script_path) - self._check_file_in_docker(container_id, serach_script_path) - self._check_file_in_docker(container_id, terminate_script_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker( - container_id, log_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - compress_script_path = self.config["elasticsearch"]["compress_script_path"] - dataset_path = self.config["elasticsearch"]["dataset_path"] - try: - result = subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - f'python3 {compress_script_path} "{dataset_path}"', - ], - stderr=subprocess.PIPE, - check=True, - text=True, - ) - output = result.stderr - decompressed_size_match = re.search(r"Original size for \S+ is (\d+)", output) - compressed_size_match = re.search(r"Compressed size for \S+ is (\d+)", output) - ratio_match = re.search(r"Compression ratio for \S+ is (\d+\.\d+)", output) - ingest_e2e_match = re.search(r"Ingestion time for \S+ is (\d+\.\d+) s", output) - if decompressed_size_match: - self.benchmarking_reseults[mode].decompressed_size = ( - f"{int(decompressed_size_match.group(1)) / 1024 / 1024}MB" - ) - logger.info( - f"File size before compression: {self.benchmarking_reseults[mode].decompressed_size}" - ) - else: - logger.error("Cannot get decompressed metric") - if compressed_size_match: - self.benchmarking_reseults[mode].compressed_size = ( - f"{int(compressed_size_match.group(1)) / 1024 / 1024}MB" - ) - logger.info( - f"File size after compression: {self.benchmarking_reseults[mode].compressed_size}" - ) - else: - logger.error("Cannot get compressed metric") - if ratio_match: - self.benchmarking_reseults[mode].ratio = f"{ratio_match.group(1)}x" - logger.info(f"Compression ratio: {self.benchmarking_reseults[mode].ratio}") - else: - logger.error("Cannot get compression ratio metric") - if ingest_e2e_match: - self.benchmarking_reseults[mode].ingest_e2e_latency = ( - f"{ingest_e2e_match.group(1)}s" - ) - logger.info( - f"Elasticsearch compressed data in {dataset_path} successfully in {ingest_e2e_match.group(1)} seconds" - ) - else: - logger.error("Cannot get ingest end-to-end latency metric") - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to compress data: {e}") - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for Elasticsearch") - container_id = self.config["elasticsearch"]["container_id"] - search_script_path = self.config["elasticsearch"]["search_script_path"] - queries = self.config["elasticsearch"]["queries"] - for query in queries: - command = f"docker exec {container_id} python3 {search_script_path} '{query}'" - self._execute_query(mode, command) - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching Elasticsearch") - try: - container_id = self.config["elasticsearch"]["container_id"] - launch_script_path = self.config["elasticsearch"]["launch_script_path"] - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"bash {launch_script_path}"], - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT, - check=True, - ) - logger.info(f"Elasticsearch launched successfully in container {container_id}") - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to launch: {e}") - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating Elasticsearch") - try: - container_id = self.config["elasticsearch"]["container_id"] - terminate_script_path = self.config["elasticsearch"]["terminate_script_path"] - subprocess.run( - ["docker", "exec", container_id, "bash", "-c", f"bash {terminate_script_path}"], - check=True, - ) - except subprocess.CalledProcessError as e: - raise Exception(f"Elasticsearch failed to terminate: {e}") - - # def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - # if BenchmarkingSystemMetric.MEMORY == metric: - # container_id = self.config['elasticsearch']['container_id'] - # memory_polling_script_path = self.config['elasticsearch']['memory_polling_script_path'] - # metric_sample = -1 - # # In poll_mem.py, when there is an exception, it will print -1 - # while (-1 == metric_sample): - # result = subprocess.run( - # ['docker', 'exec', container_id, 'python3', memory_polling_script_path], - # stdout=subprocess.PIPE, - # check=True - # ) - # metric_sample = int(result.stdout.decode('utf-8').strip()) / 1024 - # if -1 == metric_sample: - # time.sleep(1) - # elif BenchmarkingSystemMetric.CPU == metric: - # metric_sample = 0 - # # TODO - # else: - # raise Exception(f"Unknow metric: {metric.value[0]}") - # return metric_sample - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - container_id = self.config["elasticsearch"]["container_id"] - try: - command = f"docker exec {container_id} ps aux" - result = subprocess.run(command, stdout=subprocess.PIPE, shell=True, check=True) - output = result.stdout.decode("utf-8").strip().split("\n") - metric = 0 - for line in output: - if "/usr/share/elasticsearch" in line: - metric += int(line.strip().split()[5]) - return metric - except subprocess.CalledProcessError: - raise Exception("Elasticsearch failed to get mem usage info") diff --git a/src/clp_bench/executor.py b/src/clp_bench/executor.py index c407aa2..4232de6 100644 --- a/src/clp_bench/executor.py +++ b/src/clp_bench/executor.py @@ -3,271 +3,421 @@ import subprocess import threading import time -from abc import ABC, abstractmethod from enum import Enum -from typing import Dict, List +from threading import Thread +from typing import Dict, List, Tuple import yaml -# Retrive logger logger = logging.getLogger(__name__) class BenchmarkingMode(Enum): - """ - Benchmarking mode marcos. + """This class defines the modes in which the CLP Bench can operate, each representing a distinct + way to execute and analyze the benchmarking process """ - HOT_RUN_MODE = "hot run" COLD_RUN_MODE = "cold run" - QUERY_ONLY_RUN_MODE = "query only run" + """Represents a "cold run" where caches are typically cleared to simulate + a cold start scenario. This has a string value "cold run" + """ + HOT_RUN_MODE = "hot run" + """Represents a "hot run" where the system may utilize warmed caches. This + has a string value "hot run" + """ + INGEST_MODE = "ingest" + """Represents an "ingest" mode, focusing on data ingestion without querying. + This has a string value "ingest" + """ class BenchmarkingStage(Enum): - """ - Benchmarking stage marcos + """This class defines the stages in the benchmarking workflow, each representing a specific + phase of operation in CLP Bench. Note that currently this class has no use, but leave here for + reserving flexibility """ INGEST = "ingest" + """Represents the ingestion stage, where data is ingested into the system for benchmarking. + This has a string value "ingest" + """ RUN_QUERY_BENCHMARK = "run_query_benchmark" + """Represents the query benchmarking stage, where performance is measured based on query + execution. This has a string value "run_query_benchmark" + """ class BenchmarkingSystemMetric(Enum): - """ - Benchmarking system metric marcos + """This class defines the system metrics that are measured during benchmarking, providing a + standardized way to reference each metric along with its unit + + TODO: We are planning to add CPU metric anon """ - MEMORY = ("memory", "KB") + MEMORY = ("memory", "B") + """Represents memory usage, measured in bytes (B) + """ class BenchmarkingResult: + """This class encapsulates various benchmarking results, such as file sizes, compression ratios, + and latency measurements, as well as system metrics collected during benchmarking stages. It + provides methods for formatting size and latency values for display. """ - Benchmarking result data structure, for visualization. + + SIZE_PRECISION: int = 0 + """Used for file size and memory usage, unit: B + """ + TIME_PRECISION: int = 0 + """Used for latency, unit: ms """ - def __init__( - self, mode: str, compressed_size="", decompressed_size="", ratio="", ingest_e2e_latency="" - ): - self.mode: str = mode - self.compressed_size: str = compressed_size - self.decompressed_size: str = decompressed_size - self.ratio: str = ratio - self.ingest_e2e_latency: str = ingest_e2e_latency - self.query_e2e_latencies = [] + @staticmethod + def format_size_result(byte: int) -> str: + """Formats the file size from bytes to a string with the specified + precision + + :param byte: The size in bytes + :return: The formatted file size as a string with units in B + """ + + return f"{byte:.{BenchmarkingResult.SIZE_PRECISION}f} B" + + @staticmethod + def format_latency_result(ns: int) -> str: + """Formats the latency from nanoseconds to milliseconds with the specified + precision + + :param ns: The latency in nanoseconds + :return: The formatted latency as a string with units in milliseconds + """ + + ns_to_ms = 1 / 1e6 + return f"{(ns * ns_to_ms):.{BenchmarkingResult.TIME_PRECISION}f} ms" + + def __init__(self): + """Constructor""" class SystemMetricResult: - def __init__(self, metric: BenchmarkingSystemMetric): - self.metric = metric - self.result_baseline = ( - 0 # The OS has used how much memory etc. 0: need baseline, -1: no baseline - ) + """Helper class to store system metric results for each stage of + benchmarking. + """ + + def __init__(self): + """Constructor""" self.stage_results: Dict[BenchmarkingStage, List] = {} + """ + A dictionary mapping each stage to a list of metric values + """ + for stage in BenchmarkingStage: self.stage_results[stage] = [] + self.compressed_size: str = "" + """The compressed file size, formatted as a string, defaults to "" + """ + self.decompressed_size: str = "" + """The compression ratio, formatted as a string, defaults to "" + """ + self.ingest_e2e_latency: str = "" + """The end-to-end latency for ingestion, formatted as a string, defaults to "" + """ + self.query_e2e_latencies: List[str] = [] + """A list of latency values for each query execution in the benchmarking run + """ + self.ratio: str = "" + """The end-to-end latency for ingestion, formatted as a string, defaults to "" + """ self.system_metric_results: Dict[BenchmarkingSystemMetric, SystemMetricResult] = {} + """A dictionary holding system metric results for different benchmarking stages + """ + for metric in BenchmarkingSystemMetric: - self.system_metric_results[metric] = SystemMetricResult(metric) + self.system_metric_results[metric] = SystemMetricResult() -class CPTExecutorBase(ABC): +class BenchmarkingEssentials: + """This class holds common information required for benchmarking in a containerized environment, + including paths to scripts for various stages of the benchmarking process and the container ID. + It verifies the existence of each required script path within the container to ensure that all + dependencies are available for execution. """ - Namespace for all essential CPT workflow steps. A base class. - Different tools that to be benchmarked might need to implement - their own executor based on this base class, which works in a - SPI manner. + def __init__( + self, + clear_cache_script_path: str, + container_id: str, + datasets_path: str, + ingest_script_path: str, + launch_script_path: str, + measure_compressed_size_script_path: str, + measure_decompressed_size_script_path: str, + reset_script_path: str, + search_script_path: str, + terminate_script_path: str, + ): + """Constructor + + :param clear_cache_script_path: Path to the script that clears the system's cache + :param container_id: The ID of the Docker container where benchmarking is performed + :param datasets_path: Path to the datasets used for benchmarking + :param ingest_script_path: Path to the script that ingests data into the system + :param launch_script_path: Path to the script that launches the benchmarking process + :param measure_compressed_size_script_path: Path to the script that measures the compressed + dataset size + :param measure_decompressed_size_script_path: Path to the script that measures the + decompressed dataset size + :param reset_script_path: Path to the script that resets the container environment + :param search_script_path: Path to the script that performs search queries in the + benchmarking process + :param terminate_script_path: Path to the script that terminates the benchmarking process + """ + + self.clear_cache_script_path = clear_cache_script_path + """Path to the script that clears the system's cache + """ + self.container_id = container_id + """The ID of the Docker container where benchmarking is performed + """ + self.datasets_path = datasets_path + """Path to the datasets used for benchmarking + """ + self.ingest_script_path = ingest_script_path + """Path to the script that ingests data into the system + """ + self.launch_script_path = launch_script_path + """Path to the script that launches the benchmarking process + """ + self.measure_compressed_size_script_path = measure_compressed_size_script_path + """Path to the script that measures the compressed dataset size + """ + self.measure_decompressed_size_script_path = measure_decompressed_size_script_path + """Path to the script that measures the decompressed dataset size + """ + self.reset_script_path = reset_script_path + """Path to the script that resets the container environment + """ + self.search_script_path = search_script_path + """Path to the script that performs search queries in the benchmarking process + """ + self.terminate_script_path = terminate_script_path + """Path to the script that terminates the benchmarking process + """ + + self.__check_path(clear_cache_script_path) + self.__check_path(ingest_script_path) + self.__check_path(launch_script_path) + self.__check_path(measure_compressed_size_script_path) + self.__check_path(measure_decompressed_size_script_path) + self.__check_path(reset_script_path) + self.__check_path(search_script_path) + self.__check_path(terminate_script_path) + + def __check_path(self, script_path: str): + """Checks if a script path exists in the container and logs the result + + For all scripts we need to check if they are there. Note that since datasets_path could + also be a pattern, we don't check its existence. + + :param script_path: The path of the script to check + + :raise Exception: If the script does not exist in the container + """ + + try: + subprocess.run( + f"docker exec {self.container_id} test -e {script_path}", + shell=True, + check=True, + ) + logger.info(f"{script_path} exists in container {self.container_id}") + except subprocess.CalledProcessError: + raise Exception(f"{script_path} does not exist in container {self.container_id}") + + +class BenchmarkingSystemMetricPoller: + """This class manages a separate thread to periodically poll a specific system metric (e.g., + memory usage) for each stage of the benchmarking process. Each stage has its own polling + interval and event to control when polling should be active. """ - def __init__(self, config_path: str) -> None: - super().__init__() - self.config = None - with open(config_path, "r") as config_file: - self.config = yaml.safe_load(config_file) - if self.config is None: - raise Exception("Unable to parse " + config_path) - # Results for different modes - self.benchmarking_reseults: Dict[BenchmarkingMode, BenchmarkingResult] = {} - for mode in BenchmarkingMode: - self.benchmarking_reseults[mode] = BenchmarkingResult(mode) + def __init__(self, metric: BenchmarkingSystemMetric): + """Constructor + + :param metric: The system metric being tracked (e.g., memory) + """ + self.metric = metric + + self.stage_alteration_notifier = threading.Event() + """Event used to notify when the stage changes, signaling the polling thread to adjust + behavior accordingly + """ + self.stage_events: Dict[BenchmarkingStage, threading.Event] = {} + """Dictionary mapping each benchmarking stage to an event that controls the polling + activity during that stage + """ + self.stage_polling_intervals: Dict[BenchmarkingStage, int] = {} + """Dictionary mapping each benchmarking stage to its polling interval, in seconds + """ + self.thread: threading.Thread = Thread() + """The thread responsible for polling the metric + """ - self.__overall_threading_event = threading.Event() + for stage in BenchmarkingStage: + self.stage_polling_intervals[stage] = 10 + self.stage_events[stage] = threading.Event() - class SystemMetricPoller: - def __init__(self, metric: BenchmarkingSystemMetric): - self.metric = metric - self.thread: threading.Thread = None - self.stage_alteration_notifier = threading.Event() - self.stage_polling_intervals: Dict[BenchmarkingStage, int] = {} - self.stage_events: Dict[BenchmarkingStage, threading.Event] = {} - for stage in BenchmarkingStage: - self.stage_polling_intervals[stage] = 10 - self.stage_events[stage] = threading.Event() - self.__system_metric_pollers: Dict[BenchmarkingSystemMetric, SystemMetricPoller] = {} - for metric in BenchmarkingSystemMetric: - self.__system_metric_pollers[metric] = SystemMetricPoller(metric) +class ClpBenchExecutor: + """This class is responsible for setting up, executing, and collecting results for benchmarking + processes in a containerized environment. It includes methods for launching, ingesting data, + running queries, and visualizing results. Additionally, it manages system metric polling + for each benchmarking stage and mode. + """ - # The following are some utils - def _check_file_in_docker(self, container_id: str, file_path: str) -> None: - try: - subprocess.run(["docker", "exec", container_id, "test", "-f", file_path], check=True) - logger.info(f"{file_path} exists in container {container_id}") - except subprocess.CalledProcessError: - raise Exception(f"{file_path} does not exist in container {container_id}") + def __init__(self, assets_path: str) -> None: + """Constructor - def _check_directory_in_docker( - self, container_id: str, directory_path: str, need_to_create=True, need_to_clear=False - ) -> None: - try: - subprocess.run( - ["docker", "exec", container_id, "test", "-d", directory_path], check=True - ) - logger.info(f"{directory_path} exists in {container_id}") - if need_to_clear: - logger.info( - f"Clearing existing stuff in {directory_path} in container {container_id}" - ) - try: - # Note to myself: when running the command manually in a shell, the wildcard (*) is expanded - # by the shell to match files in the directory. However, when you run it through - # `subprocess.run`, there is no shell involved by default, so the wildcard (*) isn’t expanded - # and remains a literal *, which won’t work as expected. So the solution is to use `bash -c` - # to enable wildcard expansion. - subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - f"rm -rf {directory_path}/*", - ], - check=True, - ) - logger.info( - f"All contents within {directory_path} cleared successfully in container {container_id}" - ) - except subprocess.CalledProcessError as e: - raise Exception( - f"Failed to clear {directory_path} contents in {container_id}: {e}" - ) - except subprocess.CalledProcessError as e1: - if need_to_create: - logger.info(f"{directory_path} does not exist in {container_id}, try to create one") - try: - subprocess.run( - ["docker", "exec", container_id, "mkdir", "-p", directory_path], check=True - ) - logger.info( - f"{directory_path} created successfully in container {container_id}" - ) - except subprocess.CalledProcessError as e2: - raise Exception( - f"{directory_path} failed to create in container {container_id}: {e2}" - ) - else: - raise Exception(f"{directory_path} does not exist in {container_id}: {e1}") - - def _get_mem_usage_from_docker_stats(self, line: str) -> float: - mem_usage = line.strip().split()[3] - if "GiB" in mem_usage: - return float(mem_usage.split("GiB")[0]) * 1024 * 1024 - elif "MiB" in mem_usage: - return float(mem_usage.split("MiB")[0]) * 1024 - elif "KB" in mem_usage: - return float(mem_usage.split("KB")[0]) - else: - return float(mem_usage.split("B")[0]) / 1024 + :param assets_path: The string of the path to assets of the tool to benchmark + """ - def _execute_query(self, mode: BenchmarkingMode, command: str): - wc_command = f"{command} | wc -l" - logger.info(f"Executing command: {wc_command}") - start_ts = time.perf_counter_ns() - result = subprocess.run( - wc_command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True, check=True - ) - end_ts = time.perf_counter_ns() - elapsed_time = (end_ts - start_ts) / 1e9 - nr_matched_log_lines = int(result.stdout.decode("utf-8").strip()) - logger.info(f"Number of matched log lines: {nr_matched_log_lines}") - self.benchmarking_reseults[mode].query_e2e_latencies.append(f"{elapsed_time:.9f}s") + super().__init__() + self.__benchmarking_essentials: BenchmarkingEssentials + """Stores essential configuration and script paths + """ + self.__benchmarking_results: Dict[BenchmarkingMode, BenchmarkingResult] = {} + """Stores results for each mode + """ + self.__hot_run_warm_up_times: int + """Number of warm-up runs for hot-run mode + """ + self.__overall_threading_event = threading.Event() + """Event to control the start and stop of metric polling + """ + self.__queries: List[str] + """List of queries to be executed during benchmarking + """ + self.__related_processes: List[str] + """List of related processes for monitoring metrics + """ + self.__system_metric_enable: bool + """Flag indicating whether system metric polling is enabled + """ + self.__system_metric_pollers: Dict[BenchmarkingSystemMetric, BenchmarkingSystemMetricPoller] + """Dictionary of pollers for each system metric + """ + + self.__load_benchmarking_essentials_config(assets_path) + for mode in BenchmarkingMode: + self.__benchmarking_results[mode] = BenchmarkingResult() - def __set_thread_event_for_stage(self, stage: BenchmarkingStage): - for it_stage in BenchmarkingStage: - for it_metric in BenchmarkingSystemMetric: - if stage != it_stage: - self.__system_metric_pollers[it_metric].stage_events[it_stage].clear() - else: - self.__system_metric_pollers[it_metric].stage_events[it_stage].set() - self.__system_metric_pollers[it_metric].stage_alteration_notifier.set() - self.__system_metric_pollers[it_metric].stage_alteration_notifier.clear() + def launch(self, mode: BenchmarkingMode): + """Launches the benchmarking process for the specified mode - def __unset_thread_event_after_stage(self, stage: BenchmarkingStage): - for it_stage in BenchmarkingStage: - for it_metric in BenchmarkingSystemMetric: - if stage != it_stage: - self.__system_metric_pollers[it_metric].stage_events[it_stage].clear() - else: - self.__system_metric_pollers[it_metric].stage_events[it_stage].clear() - self.__system_metric_pollers[it_metric].stage_alteration_notifier.set() - self.__system_metric_pollers[it_metric].stage_alteration_notifier.clear() + :param mode: The mode to launch + """ + + self.__execute_script(self.__benchmarking_essentials.launch_script_path) + if BenchmarkingMode.INGEST_MODE == mode: + self.__execute_script(self.__benchmarking_essentials.reset_script_path) + + def terminate(self): + """Terminates the benchmarking process for the specified mode""" - # The following are the main SPI - @abstractmethod - def deploy(self, mode: BenchmarkingMode): - pass + self.__execute_script(self.__benchmarking_essentials.terminate_script_path) - @abstractmethod def ingest(self, mode: BenchmarkingMode): + """Runs the ingestion phase, recording compressed and decompressed sizes and latency + + :param mode: The benchmarking mode for which ingestion is run + """ + self.__set_thread_event_for_stage(BenchmarkingStage.INGEST) - pass + self.__benchmarking_results[mode].decompressed_size = BenchmarkingResult.format_size_result( + int( + self.__execute_script( + self.__benchmarking_essentials.measure_decompressed_size_script_path, + (self.__benchmarking_essentials.datasets_path,), + ) + ) + ) + start_ts = time.perf_counter_ns() + self.__execute_script( + self.__benchmarking_essentials.ingest_script_path, + (self.__benchmarking_essentials.datasets_path,), + ) + end_ts = time.perf_counter_ns() + self.__benchmarking_results[mode].compressed_size = BenchmarkingResult.format_size_result( + int( + self.__execute_script( + self.__benchmarking_essentials.measure_compressed_size_script_path + ) + ) + ) + self.__benchmarking_results[mode].ingest_e2e_latency = ( + BenchmarkingResult.format_latency_result(end_ts - start_ts) + ) - @abstractmethod def run_query_benchmark(self, mode: BenchmarkingMode): - self.__set_thread_event_for_stage(BenchmarkingStage.RUN_QUERY_BENCHMARK) - pass - - @abstractmethod - def launch(self, mode: BenchmarkingMode): - pass + """Runs the query benchmarking phase and records end-to-end latencies for each query - @abstractmethod - def mid_terminate(self, mode: BenchmarkingMode): - self.__unset_thread_event_after_stage(BenchmarkingStage.INGEST) - pass + :param mode: The benchmarking mode for which query benchmarking is run + """ - @abstractmethod - def terminate(self, mode: BenchmarkingMode): - pass + self.__set_thread_event_for_stage(BenchmarkingStage.RUN_QUERY_BENCHMARK) + for query in self.__queries: + if BenchmarkingMode.COLD_RUN_MODE == mode: + logger.info("Clearing page cache") + self.__execute_script(self.__benchmarking_essentials.clear_cache_script_path) + elif BenchmarkingMode.HOT_RUN_MODE == mode: + for i in range(self.__hot_run_warm_up_times): + self.__execute_script( + self.__benchmarking_essentials.search_script_path, (query,) + ) + start_ts = time.perf_counter_ns() + query_result = self.__execute_script( + self.__benchmarking_essentials.search_script_path, (query,) + ) + end_ts = time.perf_counter_ns() + nr_matched_log_lines = int(query_result) + logger.info(f"Number of matched log lines: {nr_matched_log_lines}") + self.__benchmarking_results[mode].query_e2e_latencies.append( + BenchmarkingResult.format_latency_result(end_ts - start_ts) + ) def visualize(self): - for mode, result in self.benchmarking_reseults.items(): + """ + Logs the benchmarking results for each mode, including decompressed/compressed sizes, + ratios, ingestion latency, and query latencies + """ + + for mode, result in self.__benchmarking_results.items(): if result.decompressed_size: logger.info( - f"{mode.value.capitalize()} mode: decompressed size {result.decompressed_size}" + f"{mode.value.capitalize()} mode: decompressed size " + f"{result.decompressed_size}" ) if result.compressed_size: logger.info( - f"{mode.value.capitalize()} mode: compressed size {result.compressed_size}" + f"{mode.value.capitalize()} mode: " f"compressed size {result.compressed_size}" ) if result.ratio: - logger.info(f"{mode.value.capitalize()} mode: compression ratio {result.ratio}") + logger.info(f"{mode.value.capitalize()} mode: " f"compression ratio {result.ratio}") if result.ingest_e2e_latency: logger.info( - f"{mode.value.capitalize()} mode: ingest e2e latency {result.ingest_e2e_latency}" + f"{mode.value.capitalize()} mode: ingest e2e latency " + f"{result.ingest_e2e_latency}" ) for i in range(len(result.query_e2e_latencies)): logger.info( - f"{mode.value.capitalize()} mode: No.{i} query e2e latency {result.query_e2e_latencies[i]}" + f"{mode.value.capitalize()} mode: No.{i} query e2e latency " + f"{result.query_e2e_latencies[i]}" ) - if self.config.get("system_metric", {}).get("enable", False): + if self.__system_metric_enable: for metric in BenchmarkingSystemMetric: for stage in BenchmarkingStage: - if not result.system_metric_results[metric].stage_results[stage]: - average_metric_result = 0 - else: + if result.system_metric_results[metric].stage_results[stage]: result.system_metric_results[metric].stage_results[stage] = [ result for result in result.system_metric_results[metric].stage_results[ @@ -275,107 +425,220 @@ def visualize(self): ] if 0 < result ] - if -1 != result.system_metric_results[metric].result_baseline: - average_metric_result = int( - statistics.mean( - result.system_metric_results[metric].stage_results[stage] - ) - - result.system_metric_results[metric].result_baseline - ) - else: - average_metric_result = int( - statistics.mean( - result.system_metric_results[metric].stage_results[stage] - ) + average_metric_result = int( + statistics.mean( + result.system_metric_results[metric].stage_results[stage] ) + ) logger.info( - f"{mode.value.capitalize()} mode: average {metric.value[0]} usage at {stage.value} stage: {average_metric_result}{metric.value[1]}" + f"{mode.value.capitalize()} mode: average {metric.value[0]} usage " + f"at {stage.value} stage: {average_metric_result} {metric.value[1]}" ) - def __load_system_metric_polling_config(self, metric: BenchmarkingSystemMetric): - for stage in BenchmarkingStage: - interval = ( - self.config.get("system_metric", {}) - .get(metric.value[0], {}) - .get(f"{stage.value}_polling_interval", 10) + def start_polling_system_metric(self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode): + """Starts a polling thread for monitoring the specified system metric in the given mode + + :param metric: The system metric to monitor + :param mode: The mode in which the polling should be activated + """ + + if not self.__system_metric_enable: + return + if not self.__overall_threading_event.is_set(): + logger.info(f"Start polling {metric.value[0]} usage for mode {mode.value}") + self.__overall_threading_event.set() + self.__system_metric_pollers[metric].thread = threading.Thread( + target=self.__poll_system_metrics, + args=( + metric, + mode, + ), + daemon=True, ) - self.__system_metric_pollers[metric].stage_polling_intervals[stage] = interval + self.__system_metric_pollers[metric].thread.start() + else: + logger.error(f"Already being polling {metric.value[0]} usage for mode {mode.value}") + + def stop_polling_system_metric(self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode): + """Stops the polling thread for the specified system metric in the given mode + + :param metric: The system metric to stop monitoring + :param mode: The mode for which the polling should be stopped + """ + + if not self.__system_metric_enable: + return + if self.__overall_threading_event.is_set(): + logger.info(f"Stop polling {metric.value[0]} usage for mode {mode.value}") + self.__overall_threading_event.clear() + else: + logger.error(f"Already stopped polling {metric.value[0]} usage for mode {mode.value}") + + def __load_benchmarking_essentials_config(self, assets_path: str): + """This method reads configuration details from `config.yaml` located in the specified + assets path, and initializes :class:`BenchmarkingEssentials` and other parameters + required for benchmarking. + + :param assets_path: The path to the directory containing the `config.yaml` configuration + file + :raise Exception: If the configuration file cannot be parsed or essential configurations are + missing. + """ + + config_path = f"{assets_path}/config.yaml" + with open(config_path, "r") as config_file: + config = yaml.safe_load(config_file) + if config is None: + raise Exception("Unable to parse " + config_path) + assets_path_in_container = config["assets_path"] + self.__benchmarking_essentials = BenchmarkingEssentials( + clear_cache_script_path=f"{assets_path_in_container}/clear-cache.sh", + container_id=config["container_id"], + datasets_path=config["datasets_path"], + ingest_script_path=f"{assets_path_in_container}/ingest.sh", + launch_script_path=f"{assets_path_in_container}/launch.sh", + measure_compressed_size_script_path=f"{assets_path_in_container}/measure-compressed-size.sh", + measure_decompressed_size_script_path=f"{assets_path_in_container}/measure-decompressed-size.sh", + reset_script_path=f"{assets_path_in_container}/reset.sh", + search_script_path=f"{assets_path_in_container}/search.sh", + terminate_script_path=f"{assets_path_in_container}/terminate.sh", + ) + self.__hot_run_warm_up_times: int = config.get("hot_run_warm_up_times", 3) + self.__queries: List[str] = config["queries"] + self.__related_processes: List[str] = config["related_processes"] + self.__system_metric_enable = config.get("system_metric", {}).get("enable", False) + self.__system_metric_pollers = {} + for metric in BenchmarkingSystemMetric: + self.__system_metric_pollers[metric] = BenchmarkingSystemMetricPoller(metric) + for stage in BenchmarkingStage: + for metric in BenchmarkingSystemMetric: + interval = ( + config.get("system_metric", {}) + .get(metric.value[0], {}) + .get(f"{stage.value}_polling_interval", 10) + ) + self.__system_metric_pollers[metric].stage_polling_intervals[stage] = interval + logger.info( + f"{metric.value[0].capitalize()} usage polling interval for {stage.value}: " + f"{interval} seconds" + ) + + def __set_thread_event_for_stage(self, stage: BenchmarkingStage): + """Sets or clears events for each benchmarking stage based on the current stage + + :param stage: The current stage for which events are to be set + """ + + for it_stage in BenchmarkingStage: + for it_metric in BenchmarkingSystemMetric: + if stage != it_stage: + self.__system_metric_pollers[it_metric].stage_events[it_stage].clear() + else: + self.__system_metric_pollers[it_metric].stage_events[it_stage].set() + self.__system_metric_pollers[it_metric].stage_alteration_notifier.set() + self.__system_metric_pollers[it_metric].stage_alteration_notifier.clear() + + def __execute_script(self, script_path: str, args: Tuple[str] = ()) -> str: + """Executes a script in the Docker container and returns its output + + :param script_path: Path to the script to be executed in the container + :param args : Additional arguments for the script. Defaults to an empty list + + :return: Output from the script execution + :raise subprocess.CalledProcessError: If the script execution fails + """ + + try: logger.info( - f"{metric.value[0].capitalize()} usage polling interval for {stage.value}: {interval} seconds" + f"Executing script@{script_path} in container: " + f"{self.__benchmarking_essentials.container_id}" ) + command = ( + f"docker exec {self.__benchmarking_essentials.container_id} bash {script_path}" + ) + for arg in args: + command += f" {arg}" + result = subprocess.run( + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, check=True + ) + logger.debug(result) + return result.stdout.decode("utf-8").strip() + except subprocess.CalledProcessError as e: + logger.error( + f"Failed to execute script@{script_path} in container: " + f"{self.__benchmarking_essentials.container_id}" + ) + raise e def __record_system_metric_polling_sample( self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode ): + """This method captures a sample of the specified metric (e.g., memory usage) if the event + for the current stage is set, and appends the sample to the benchmarking results for the + specified mode and stage + + Note that only one stage's event should be set at any given time, and the method will + wait for the next polling interval before continuing + + :param metric: The system metric being polled + :param mode: The benchmarking mode for which the metric is being recorded. + """ for stage in BenchmarkingStage: if self.__system_metric_pollers[metric].stage_events[stage].is_set(): - metric_sample = self._acquire_system_metric_sample(metric) - self.benchmarking_reseults[mode].system_metric_results[metric].stage_results[ + metric_sample = self.__acquire_system_metric_sample(metric) + if 0 >= metric_sample: + break + self.__benchmarking_results[mode].system_metric_results[metric].stage_results[ stage ].append(metric_sample) logger.info( - f"Current {metric.value[0]} usage at {stage.value} stage: {metric_sample}{metric.value[1]}" + f"Current {metric.value[0]} usage at {stage.value} stage: {metric_sample}" + f" {metric.value[1]}" ) self.__system_metric_pollers[metric].stage_alteration_notifier.wait( self.__system_metric_pollers[metric].stage_polling_intervals[stage] ) break # Only one stage's event should be set at any time - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: + def __acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: + """This method executes a command in the Docker container to capture the specified metric + (e.g., memory usage), which is used for performance monitoring + + :param metric: The system metric to acquire + :return: The sample value of the metric, in bytes if it's memory + :raise Exception: If the metric is unknown or unsupported + """ + if BenchmarkingSystemMetric.MEMORY == metric: - with open("/proc/meminfo", "r") as f: - mem_total = 0 - mem_free = 0 - for line in f.readlines(): - if "MemTotal" in line: - mem_total = int(line.strip().split()[1]) - elif "MemFree" in line: - mem_free = int(line.strip().split()[1]) - if 0 != mem_total and 0 != mem_free: - break - metric_sample = mem_total - mem_free - elif BenchmarkingSystemMetric.CPU == metric: + kb_to_b = 1024 + result = subprocess.run( + f"docker exec {self.__benchmarking_essentials.container_id} ps aux", + check=True, + shell=True, + stdout=subprocess.PIPE, + ) + output = result.stdout.decode("utf-8").strip().split("\n") metric_sample = 0 - # TODO + for line in output: + process = line.strip().split()[10].strip() + flag = False + for related_process in self.__related_processes: + if related_process.startswith(process): + flag = True + break + if flag: + metric_sample += int(line.strip().split()[5]) * kb_to_b + return metric_sample else: - raise Exception(f"Unknow metric: {metric.value[0]}") - return metric_sample + raise Exception(f"Unknown metric: {metric.value[0]}") def __poll_system_metrics(self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode): - self.__load_system_metric_polling_config(metric) + """This method runs in a separate thread, polling the specified metric at regular intervals + as long as the `__overall_threading_event` is set, and records each sample + + :param metric: The system metric to poll + :param mode: The benchmarking mode in which the metric is being polled + """ while self.__overall_threading_event.is_set(): self.__record_system_metric_polling_sample(metric, mode) - - def start_polling_system_metric(self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode): - if not self.config.get("system_metric", {}).get("enable", False): - return - if not self.__overall_threading_event.is_set(): - logger.info(f"Start polling {metric.value[0]} usage for mode {mode.value}") - if 0 == self.benchmarking_reseults[mode].system_metric_results[metric].result_baseline: - metric_sample = self._acquire_system_metric_sample(metric) - self.benchmarking_reseults[mode].system_metric_results[ - metric - ].result_baseline = metric_sample - logger.info(f"Initial {metric.value[0]} usage: {metric_sample}{metric.value[1]}") - self.__overall_threading_event.set() - self.__system_metric_pollers[metric].thread = threading.Thread( - target=self.__poll_system_metrics, - args=( - metric, - mode, - ), - daemon=True, - ) - self.__system_metric_pollers[metric].thread.start() - else: - logger.error(f"Already being polling {metric.value[0]} usage for mode {mode.value}") - - def stop_polling_system_metric(self, metric: BenchmarkingSystemMetric, mode: BenchmarkingMode): - if not self.config.get("system_metric", {}).get("enable", False): - return - if self.__overall_threading_event.is_set(): - logger.info(f"Stop polling {metric.value[0]} usage for mode {mode.value}") - self.__overall_threading_event.clear() - else: - logger.error(f"Already stopped polling {metric.value[0]} usage for mode {mode.value}") diff --git a/src/clp_bench/glt_executor.py b/src/clp_bench/glt_executor.py deleted file mode 100644 index ab5eeea..0000000 --- a/src/clp_bench/glt_executor.py +++ /dev/null @@ -1,133 +0,0 @@ -import logging -import subprocess -import time - -from .executor import BenchmarkingMode, BenchmarkingSystemMetric, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorGLT(CPTExecutorBase): - """ - A service provider for glt, which is a binary. - """ - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying GLT") - container_id = self.config["glt"]["container_id"] - logger.info(f"glt docker container ID: {container_id}") - binary_path = self.config["glt"]["binary_path"] - logger.info(f"glt binary location: {binary_path}") - data_path = self.config["glt"]["data_path"] - logger.info(f"glt data location: {data_path}") - dataset_path = self.config["glt"]["dataset_path"] - logger.info(f"glt dataset location: {dataset_path}") - - self._check_file_in_docker(container_id, binary_path) - self._check_directory_in_docker( - container_id, data_path, need_to_create=False, need_to_clear=True - ) - self._check_directory_in_docker(container_id, data_path, need_to_create=False) - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for glt") - container_id = self.config["glt"]["container_id"] - binary_path = self.config["glt"]["binary_path"] - data_path = self.config["glt"]["data_path"] - dataset_path = self.config["glt"]["dataset_path"] - try: - result = subprocess.run( - ["du", dataset_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - decompressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].decompressed_size = f"{decompressed_size_mb:.2f}MB" - start_ts = time.perf_counter_ns() - subprocess.run( - [ - "docker", - "exec", - container_id, - "bash", - "-c", - f"{binary_path} c {data_path} {dataset_path}", - ], - check=True, - ) - end_ts = time.perf_counter_ns() - elapsed_time = (end_ts - start_ts) / 1e9 - self.benchmarking_reseults[mode].ingest_e2e_latency = f"{elapsed_time:.9f}s" - # FIXME: this is inconsistent with clp-s genereated archives permission - subprocess.run( - ["sudo", "find", data_path, "-exec", "chmod", "o+r+x", "{}", ";"], check=True - ) - result = subprocess.run( - ["du", data_path, "-c", "-b"], stdout=subprocess.PIPE, check=True - ) - compressed_size_mb = ( - int(result.stdout.decode("utf-8").split("\n")[-2].split()[0].strip()) / 1024 / 1024 - ) - self.benchmarking_reseults[mode].compressed_size = f"{compressed_size_mb:.2f}MB" - self.benchmarking_reseults[mode].ratio = f"{decompressed_size_mb / compressed_size_mb}x" - except subprocess.CalledProcessError as e: - raise Exception(f"glt failed to compress data: {e}") - pass - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for glt") - container_id = self.config["glt"]["container_id"] - binary_path = self.config["glt"]["binary_path"] - data_path = self.config["glt"]["data_path"] - queries = self.config["glt"]["queries"] - try: - for query in queries: - command = f"docker exec {container_id} {binary_path} s {data_path} {query}" - self._execute_query(mode, command) - except subprocess.CalledProcessError as e: - raise Exception(f"glt failed to finish the query benchmarking: {e}") - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching glt") - pass - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating glt") - pass - - # def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - # container_id = self.config['glt']['container_id'] - # try: - # result = subprocess.run( - # ['docker', 'stats', container_id, '--no-stream'], - # stdout=subprocess.PIPE, - # check=True - # ) - # output = result.stdout.decode('utf-8').strip().split('\n') - # for line in output: - # if container_id in line: - # return self._get_mem_usage_from_docker_stats(line) - # except subprocess.CalledProcessError as e: - # raise Exception(f"glt failed to get mem usage info") - - def _acquire_system_metric_sample(self, metric: BenchmarkingSystemMetric) -> int: - binary_path = self.config["glt"]["binary_path"] - data_path = self.config["glt"]["data_path"] - container_id = self.config["glt"]["container_id"] - try: - command = f"docker exec {container_id} ps aux" - result = subprocess.run(command, stdout=subprocess.PIPE, shell=True, check=True) - output = result.stdout.decode("utf-8").strip().split("\n") - for line in output: - if binary_path in line and data_path in line: - return int(line.strip().split()[5]) - return 0 - except subprocess.CalledProcessError: - raise Exception("clp-s failed to get mem usage info") diff --git a/src/clp_bench/grafanaloki_executor.py b/src/clp_bench/grafanaloki_executor.py deleted file mode 100644 index 12544eb..0000000 --- a/src/clp_bench/grafanaloki_executor.py +++ /dev/null @@ -1,91 +0,0 @@ -import logging -import subprocess -import time -from datetime import timedelta - -from dateutil import parser - -from .executor import BenchmarkingMode, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorGrafanaLoki(CPTExecutorBase): - """ - A service provider for Grafana Loki. - """ - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying Grafana Loki") - pass - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - logger.info("Ingesting data for Grafana Loki") - # When launched the loki and promtail containers, the ingestion is automatically started - # You could query the current ingested bytes by: - # curl -G http://localhost:3100/metrics | grep 'loki_distributor_bytes_received_total' - # You could query the current compression size by: - # curl -G http://localhost:3100/metrics | grep 'loki_chunk_store_stored_chunk_bytes_total' - # You could query the ingestion time by: - # curl -G http://localhost:3100/metrics | grep 'loki_request_duration_seconds_sum{method="POST",route="loki_api_v1_push"' - pass - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for Grafana Loki") - logcli_binary_path = self.config["loki"]["logcli_binary_path"] - job = self.config["loki"]["job"] - limit = self.config["loki"]["limit"] - batch = self.config["loki"]["batch"] - from_ts = self.config["loki"]["from"] - to_ts = self.config["loki"]["to"] - queries = self.config["loki"]["queries"] - - start_time = parser.isoparse(from_ts) - end_time = parser.isoparse(to_ts) - interval = timedelta(minutes=self.config.get("loki", {}).get("interval", 10)) - for query in queries: - current_time = start_time - total_query_latency = 0 - total_nr_matched_log_lines = 0 - while current_time <= end_time - interval: - command = ( - f"{logcli_binary_path} query " - + "'{ job=" - + f'"{job}"' - + "} |~ " - + query - + f"' --limit={limit} --batch={batch} " - + f'--from="{current_time.isoformat()}" --to="{(current_time + interval).isoformat()}" | wc -l' - ) - logger.info(f"Executing command: {command}") - start_ts = time.perf_counter_ns() - result = subprocess.run( - command, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - shell=True, - check=True, - ) - end_ts = time.perf_counter_ns() - total_query_latency += (end_ts - start_ts) / 1e9 - total_nr_matched_log_lines += int(result.stdout.decode("utf-8").strip()) - current_time += interval - logger.info(f"Number of matched log lines: {total_nr_matched_log_lines}") - self.benchmarking_reseults[mode].query_e2e_latencies.append( - f"{total_query_latency:.9f}s" - ) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching Grafana Loki") - pass - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating Grafana Loki") - pass diff --git a/src/clp_bench/grep_executor.py b/src/clp_bench/grep_executor.py deleted file mode 100644 index ea469c6..0000000 --- a/src/clp_bench/grep_executor.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging - -from .executor import BenchmarkingMode, CPTExecutorBase - -# Retrive logger -logger = logging.getLogger(__name__) - - -class CPTExecutorGrep(CPTExecutorBase): - """ - A service provider for grep, which is a binary. - """ - - def deploy(self, mode: BenchmarkingMode): - logger.info("Deploying Grep") - dataset_path = self.config["grep"]["dataset_path"] - logger.info(f"grep data location: {dataset_path}") - - def ingest(self, mode: BenchmarkingMode): - super().ingest(mode) - pass - - def run_query_benchmark(self, mode: BenchmarkingMode): - super().run_query_benchmark(mode) - logger.info("Running query benchmark for grep") - dataset_path = self.config["grep"]["dataset_path"] - queries = self.config["grep"]["queries"] - for query in queries: - command = f"grep -r {query} {dataset_path}" - self._execute_query(mode, command) - - def mid_terminate(self, mode: BenchmarkingMode): - super().mid_terminate(mode) - self.terminate(mode) - - def launch(self, mode: BenchmarkingMode): - logger.info("Launching grep") - pass - - def terminate(self, mode: BenchmarkingMode): - logger.info("Terminating grep") - pass diff --git a/ui/.env b/ui/.env index 4887a12..e7124b5 100644 --- a/ui/.env +++ b/ui/.env @@ -1,5 +1,6 @@ +SQLALCHEMY_DATABASE_URI=sqlite:///app.db VITE_BACKEND_HOST=127.0.0.1 VITE_BACKEND_PORT=5000 VITE_FRONTEND_BASE_PATH= VITE_FRONTEND_HOST=127.0.0.1 -VITE_FRONTEND_PORT=5173 \ No newline at end of file +VITE_FRONTEND_PORT=5173 diff --git a/ui/README.md b/ui/README.md index 65a13e6..349946c 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1,52 +1,64 @@ # UI + This is a web interface for viewing the benchmark results produced by `clp-bench`. ## Requirements -+ Node.js -+ Python v3.10 or higher +- Node.js +- Python v3.10 or higher ## Set up + The web interface includes a backend and a frontend. -## Backend -+ Enter the `backend` directory. -+ Create a virtual `python3` environment under `/backend`: +### Backend + +- Enter the `backend` directory. +- Create a virtual `python3` environment under `/backend`: ```shell python3 -m venv venv ``` -+ Install dependencies: +- Install dependencies: ```shell . venv/bin/activate pip install -r requirements.txt ``` -+ Run the backend: +- Run the backend: ```shell python3 app.py ``` -+ If this is the first time you've run the backend, you may also need to load the data (leave app.py - running and run the following in another window): +- If this is the first time you've run the backend, you may also need to load the data (leave + `app.py` running and run the following in another window, as `load_results.py` requires the + database to be initialized): ```shell python3 load_results.py ``` -## Frontend -* Enter the `frontend` directory. -+ Install dependencies: +### Frontend + +- Enter the `frontend` directory. +- Install dependencies: ``` npm install ``` -+ During development, you can run the frontend with: +- During development, you can run the frontend with: ``` npm run dev ``` - + The command will print out the address of the web interface. -+ In production, you can build the frontend with: + - The command will print out the address of the web interface. +- In production, you can build the frontend with: ``` npm run build ``` - + The frontend will be available through the backend's address. + - The frontend will be available through the backend's address. + +### Configuration -## Configuration There is a template `.env` file in this directory. To create a custom configuration, you may copy `.env` to `.env.local` and modify the content, which will override the settings in `.env`. + +# Benchmark results database + +We use `sqlite` to manage a database that contains all benchmarking results. `load_results.py` +automatically loads benchmarking results from the `results.json` file under the directory of each +target tool. diff --git a/ui/backend/app.py b/ui/backend/app.py index 9dce69c..df51052 100644 --- a/ui/backend/app.py +++ b/ui/backend/app.py @@ -1,14 +1,17 @@ -from flask import Flask, request, jsonify, send_from_directory +import os +from typing import Tuple + +from dotenv import load_dotenv +from flask import Flask, jsonify, request, Response from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import UniqueConstraint, Row +from sqlalchemy import UniqueConstraint from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from dotenv import load_dotenv -import os - class Base(DeclarativeBase): + """Required by SQLAlchemy""" + pass @@ -17,31 +20,56 @@ class Base(DeclarativeBase): class BenchmarkingResult(db.Model): id: Mapped[int] = mapped_column(primary_key=True) + """The ID of each result + """ target: Mapped[str] = mapped_column(nullable=False) + """The short name of the tool to benchmark, which is also used by frontend to manage the results + """ target_displayed_name: Mapped[str] = mapped_column(nullable=False) + """The name of the benchmarked tool which will be displayed in the UI + """ displayed_order: Mapped[int] = mapped_column(nullable=False) + """The order of displayed results. The smaller the value is, the lefter the result (column) + will be + """ is_enable: Mapped[bool] = mapped_column(nullable=False) - # type-0: debug, type-1: unstructured, type-2: semi-structured + """A switch of result, typically should be True + """ type: Mapped[int] = mapped_column(nullable=False) - # metric-0: debug, metric-1: hotrun, metric-2: coldrun + """The type of the results, type-0: debug, type-1: unstructured, type-2: dynamically-structured + """ metric: Mapped[int] = mapped_column(nullable=False) - # Unit: ms + """The metric of the results, metric-0: debug, metric-1: hot run, metric-2: cold run + """ ingest_time: Mapped[int] = mapped_column(nullable=True) - # Unit: byte + """The end-to-end latency of ingestion, the unit is millisecond + """ compressed_size: Mapped[int] = mapped_column(nullable=True) - # Unit: byte + """The size of data after compression, the unit is byte + """ avg_ingest_mem: Mapped[int] = mapped_column(nullable=True) - # Unit: byte + """The average memory usage during ingesting data, the unit is byte + """ avg_query_mem: Mapped[int] = mapped_column(nullable=True) - # Unit: ms + """The average memory usage during executing queries, the unit is byte + """ query_times: Mapped[str] = mapped_column(nullable=True) + """The end-to-end latencies of queries executed during benchmarking, the unit is millisecond + """ __table_args__ = (UniqueConstraint("target", "type", "metric", name="uix_target_type_metric"),) def _define_routes(base_path: str): + """This function defines some routes of the Flask backend""" + @app.route(f"{base_path}/api/post", methods=["POST"]) - def add_result(): + def add_result() -> Tuple[Response, int]: + """This function handles the post request, which deposit the benchmark results to the SQL + database + + :return: The status of the request + """ data = request.json new_benchmarking_result = BenchmarkingResult( target=data["target"], @@ -106,12 +134,15 @@ def add_result(): db.session.commit() return jsonify({"message": "success"}), 201 - @app.route(f"{base_path}/") - def index(): - return send_from_directory(app.static_folder, "index.html") - @app.route(f"{base_path}/api/get", methods=["GET"]) - def get_results(): + def get_results() -> Tuple[Response, int]: + """This function handles the request of getting the benchmark results, which is sent by the + UI. There are three arguments can be passed in URL as the search key to get benchmark + results: target, type and metric + + :return: The query results (typically it will return all results because UI does not append + any specified query arguments + """ target = request.args.get("target") type = request.args.get("type") metric = request.args.get("metric") @@ -159,14 +190,16 @@ def get_results(): else: load_dotenv(env_path) - base_path = os.getenv("VITE_FRONTEND_BASE_PATH", "") - app = Flask(__name__, static_folder="../frontend/dist", static_url_path=f"{base_path}/") + vite_frontend_base_path = os.getenv("VITE_FRONTEND_BASE_PATH", "") + app = Flask( + __name__, static_folder="../frontend/dist", static_url_path=f"{vite_frontend_base_path}/" + ) CORS(app) - app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///app.db" + app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI", "sqlite:///app.db") db.init_app(app) - _define_routes(base_path) + _define_routes(vite_frontend_base_path) with app.app_context(): db.create_all() app.run( - port=os.getenv("VITE_BACKEND_PORT", "127.0.0.1"), host=os.getenv("VITE_BACKEND_HOST", 5000) + host=os.getenv("VITE_BACKEND_HOST", "127.0.0.1"), port=os.getenv("VITE_BACKEND_PORT", 5000) ) diff --git a/ui/backend/load_results.py b/ui/backend/load_results.py index 3b73c94..e1ab77e 100644 --- a/ui/backend/load_results.py +++ b/ui/backend/load_results.py @@ -1,12 +1,12 @@ -import requests import json import os -from dotenv import load_dotenv +import requests +from dotenv import load_dotenv -root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -env_local_path = os.path.join(root_dir, ".env.local") -env_path = os.path.join(root_dir, ".env") +ui_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +env_local_path = os.path.join(ui_root_dir, ".env.local") +env_path = os.path.join(ui_root_dir, ".env") if os.path.exists(env_local_path): load_dotenv(env_local_path) else: @@ -15,182 +15,43 @@ port = os.getenv("VITE_BACKEND_PORT", 5000) base_path = os.getenv("VITE_FRONTEND_BASE_PATH", "") url = f"http://{host}:{port}{base_path}/api/post" -print(url) -# All current results -results = [ - # Unstructured with Hotrun - ( - ("clpg", "CLP & CLG", 1, False, 1, 1, 2871750, 6406600131, 21300206633, 971736351), - (23670, 105240, 16200, 16630, 19000, 197160, 201620, 19260, 22590, 8160, 5660, 25080, 1840), - ), - ( - ("glt", "CLP", 2, True, 1, 1, 3161250, 3758274642, 1486115308, 1712198779), - (2620, 2400, 2690, 3280, 2260, 55270, 54100, 2330, 4590, 5250, 4610, 4150, 1870), - ), - ( - ( - "elasticsearch", - "Elasticsearch", - 3, - True, - 1, - 1, - 117772160, - 124610255258, - 54708970455, - 14068779008, - ), - (660, 550, 27860, 42030, 480, 1010, 100910, 570, 66480, 520, 560, 8530, 450), - ), - ( - ("loki", "Loki", 4, True, 1, 1, 8661810, 26788642161, 43078521979, 25583723479), - ( - 651500, - 764890, - 1546260, - 4950080, - 517290, - 509460, - 2790890, - 576470, - 3362330, - 450830, - 435630, - 940180, - 467020, - ), - ), - ( - ("grep", "grep", 5, True, 1, 1, 0, 0, 0, 0), - ( - 494670, - 514690, - 403070, - 373030, - 540660, - 666190, - 604870, - 640610, - 798320, - 443490, - 839100, - 670170, - 394690, - ), - ), - # Unstructured with Coldrun - ( - ("clpg", "CLP & CLG", 1, False, 1, 2, 2871750, 6406600131, 21300206633, 5158612992), - (26581, 112420, 18457, 18647, 20206, 198129, 202447, 20038, 24216, 8901, 6003, 26140, 1986), - ), - ( - ("glt", "CLP", 2, True, 1, 2, 3161250, 3758274642, 1486115308, 1149728768), - (2844, 2576, 2728, 3359, 2434, 55033, 57339, 2715, 4795, 5934, 5364, 4656, 2016), - ), - ( - ( - "elasticsearch", - "Elasticsearch", - 3, - True, - 1, - 2, - 117772160, - 124610255258, - 54708970455, - 14075236352, - ), - (1620, 3170, 37300, 47290, 510, 23820, 107570, 580, 76530, 470, 590, 10310, 490), - ), - ( - ("loki", "Loki", 4, True, 1, 2, 8661810, 26788642161, 43078521979, 26403311452), - ( - 658560, - 639740, - 1403050, - 4655970, - 389030, - 364110, - 2608620, - 412570, - 3404270, - 108990, - 108410, - 825040, - 367430, - ), - ), - ( - ("grep", "grep", 5, True, 1, 2, 0, 0, 0, 0), - ( - 494670, - 514690, - 403070, - 373030, - 540660, - 666190, - 604870, - 640610, - 798320, - 443490, - 839100, - 670170, - 394690, - ), - ), - # Semi-structured with Hotrun - ( - ("clps", "CLP-S", 1, True, 2, 1, 786770, 381346120, 466773606, 135224361), - (1260, 28440, 1180, 1730, 1410, 1190), - ), - ( - ("clpJson", "CLP-JSON", 2, False, 2, 1, 1214800, 380842803, 1731786179, 1683677512), - (6150, 12770, 5600, 5440, 5110, 5060), - ), - ( - ( - "elasticsearch", - "Elasticsearch", - 3, - True, - 2, - 1, - 19657020, - 19141618565, - 10376729068, - 5961930752, - ), - (2280, 9440, 490, 590, 2110, 1700), - ), - # Semi-structured with Coldrun - ( - ("clps", "CLP-S", 1, True, 2, 2, 786770, 381346120, 466773606, 164731290), - (1300, 28560, 1170, 1700, 1420, 1210), - ), - ( - ("clpJson", "CLP-JSON", 2, False, 2, 2, 1214800, 380842803, 1731786179, 1500491285), - (18360, 15690, 5060, 5640, 5030, 5340), - ), - ( - ( - "elasticsearch", - "Elasticsearch", - 3, - True, - 2, - 2, - 19657020, - 19141618565, - 10376729068, - 6565423104, - ), - (3050, 9270, 520, 560, 6000, 1660), - ), -] +project_root_dir = os.path.abspath(os.path.join(ui_root_dir, "..")) +assets_dir = os.path.abspath(os.path.join(project_root_dir, "assets")) +type_dirs = os.listdir(assets_dir) +results = [] +# Iterate over assets/ and get results automatically +for type_dir in type_dirs: + type_path = os.path.join(assets_dir, type_dir) + target_dirs = os.listdir(type_path) + for target_dir in target_dirs: + if "template" == target_dir: + continue + target_path = os.path.join(type_path, target_dir) + results_of_target = json.load(open(os.path.join(target_path, "results.json"), "r")) + for metric in range(1, 3): + results.append( + ( + ( + results_of_target["target"], + results_of_target["targetDisplayedName"], + results_of_target["displayedOrder"], + results_of_target["isEnable"], + results_of_target["type"], + metric, + results_of_target["ingestTime"], + results_of_target["compressedSize"], + results_of_target["avgIngestMem"], + results_of_target["metrics"][metric - 1]["avgQueryMem"], + ), + tuple(results_of_target["metrics"][metric - 1]["queryTimes"]), + ) + ) def dump_and_post(): + """This function construct the request for each benchmark result and send it to the Flask + backend""" headers = {"Content-Type": "application/json"} for result in results: payload = json.dumps( diff --git a/ui/frontend/package-lock.json b/ui/frontend/package-lock.json index b2aa987..a05434d 100644 --- a/ui/frontend/package-lock.json +++ b/ui/frontend/package-lock.json @@ -1,7 +1,7 @@ { "name": "clp-bench-ui", "version": "0.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -39,7 +39,6 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -49,12 +48,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", - "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", - "license": "MIT", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -62,32 +61,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", - "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", - "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helpers": "^7.25.7", - "@babel/parser": "^7.25.8", - "@babel/template": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.8", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -106,16 +103,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@babel/generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", - "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", - "license": "MIT", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "dependencies": { - "@babel/types": "^7.25.7", + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -125,14 +121,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", - "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -142,29 +137,26 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", - "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", - "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-simple-access": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -174,93 +166,58 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", - "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", - "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", - "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", - "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", - "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", - "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", - "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", - "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", - "license": "MIT", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dependencies": { - "@babel/types": "^7.25.8" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -270,13 +227,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz", - "integrity": "sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -286,13 +242,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz", - "integrity": "sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -302,10 +257,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", - "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", - "license": "MIT", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -314,30 +268,28 @@ } }, "node_modules/@babel/template": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", - "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", - "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -349,20 +301,17 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/types": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", - "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", - "license": "MIT", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "@babel/helper-string-parser": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -372,7 +321,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", - "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", @@ -391,7 +339,6 @@ "version": "11.13.1", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", - "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", @@ -403,14 +350,12 @@ "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "node_modules/@emotion/is-prop-valid": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", - "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -418,14 +363,12 @@ "node_modules/@emotion/memoize": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "node_modules/@emotion/react": { "version": "11.13.3", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", @@ -449,7 +392,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", - "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", @@ -461,14 +403,12 @@ "node_modules/@emotion/sheet": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT" + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { "version": "11.13.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", @@ -490,14 +430,12 @@ "node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", - "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } @@ -505,14 +443,12 @@ "node_modules/@emotion/utils": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", - "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", - "license": "MIT" + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT" + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", @@ -522,7 +458,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "aix" @@ -539,7 +474,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -556,7 +490,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -573,7 +506,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -590,7 +522,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -607,7 +538,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -624,7 +554,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -641,7 +570,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -658,7 +586,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -675,7 +602,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -692,7 +618,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -709,7 +634,6 @@ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -726,7 +650,6 @@ "mips64el" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -743,7 +666,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -760,7 +682,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -777,7 +698,6 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -794,7 +714,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -811,7 +730,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -828,7 +746,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -845,7 +762,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "sunos" @@ -862,7 +778,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -879,7 +794,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -896,7 +810,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -906,17 +819,19 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, - "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } @@ -926,7 +841,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -935,11 +849,10 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -949,7 +862,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", @@ -960,11 +872,10 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -974,7 +885,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -998,7 +908,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -1007,11 +916,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1021,17 +929,15 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "levn": "^0.4.1" }, @@ -1043,16 +949,14 @@ "version": "1.6.8", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.8" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", - "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", - "license": "MIT", + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.8" @@ -1062,7 +966,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.0.0" }, @@ -1074,45 +977,53 @@ "node_modules/@floating-ui/utils": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", - "license": "MIT" + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" }, "node_modules/@fontsource/roboto": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.0.tgz", - "integrity": "sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==", - "license": "Apache-2.0" + "integrity": "sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==" }, "node_modules/@humanfs/core": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", - "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", - "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.0", + "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -1122,11 +1033,10 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -1139,7 +1049,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1153,7 +1062,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1162,7 +1070,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1170,14 +1077,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1187,7 +1092,6 @@ "version": "5.0.0-beta.40", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@floating-ui/react-dom": "^2.0.8", @@ -1219,19 +1123,17 @@ "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.3.tgz", - "integrity": "sha512-QBQCCIMSAv6IkArTg4Hg8q2sJRhHOci8oPAlkHWFlt2ghBdy3EqyLbIELLE/bhpqhX+E/ZkPYGIUQCd5/L0owA==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.7.tgz", + "integrity": "sha512-RGzkeHNArIVy5ZQ12bq/8VYNeICEyngngsFskTJ/2hYKhIeIII3iRGtaZaSvLpXh7h3Fg3VKTulT+QU0w5K4XQ==", "dependencies": { - "@babel/runtime": "^7.25.6" + "@babel/runtime": "^7.26.0" }, "engines": { "node": ">=14.0.0" @@ -1241,7 +1143,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.3", + "@mui/material": "^6.1.7", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1255,7 +1157,6 @@ "version": "5.0.0-beta.48", "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.48.tgz", "integrity": "sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", @@ -1296,7 +1197,6 @@ "version": "5.16.6", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/utils": "^5.16.6", @@ -1323,7 +1223,6 @@ "version": "5.16.6", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -1355,7 +1254,6 @@ "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.16.6", @@ -1392,17 +1290,16 @@ } }, "node_modules/@mui/material": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.3.tgz", - "integrity": "sha512-loV5MBoMKLrK80JeWINmQ1A4eWoLv51O2dBPLJ260IAhupkB3Wol8lEQTEvvR2vO3o6xRHuXe1WaQEP6N3riqg==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.7.tgz", + "integrity": "sha512-KsjujQL/A2hLd1PV3QboF+W6SSL5QqH6ZlSuQoeYz9r69+TnyBFIevbYLxdjJcJmGBjigL5pfpn7hTGop+vhSg==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/core-downloads-tracker": "^6.1.3", - "@mui/system": "^6.1.3", - "@mui/types": "^7.2.18", - "@mui/utils": "^6.1.3", + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.7", + "@mui/system": "^6.1.7", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.7", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", @@ -1421,7 +1318,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.3", + "@mui/material-pigment-css": "^6.1.7", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1442,10 +1339,9 @@ } }, "node_modules/@mui/material/node_modules/@mui/core-downloads-tracker": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.3.tgz", - "integrity": "sha512-ajMUgdfhTb++rwqj134Cq9f4SRN8oXUqMRnY72YBnXiXai3olJLLqETheRlq3MM8wCKrbq7g6j7iWL1VvP44VQ==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.7.tgz", + "integrity": "sha512-POuIBi80BZBogQkG4PQKIGwy4QFwB+kOr+OI4k7Znh7LqMAIhwB9OC00l6M+w1GrZJYj3T8R5WX8G6QAIvoVEw==", "peer": true, "funding": { "type": "opencollective", @@ -1453,14 +1349,13 @@ } }, "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.3.tgz", - "integrity": "sha512-4JBpLkjprlKjN10DGb1aiy/ii9TKbQ601uSHtAmYFAS879QZgAD7vRnv/YBE4iBbc7NXzFgbQMCOFrupXWekIA==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/types": "^7.2.18", + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", "@types/prop-types": "^15.7.13", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1484,14 +1379,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.3.tgz", - "integrity": "sha512-XK5OYCM0x7gxWb/WBEySstBmn+dE3YKX7U7jeBRLm6vHU5fGUd7GiJWRirpivHjOK9mRH6E1MPIVd+ze5vguKQ==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.7.tgz", + "integrity": "sha512-uLbfUSsug5K0LVkv0PI6Flste3le8+6WSL2omdTiYde93P89Qr7pKr8TA6d2yXfr+Bm+SvD8/fGnkaRwFkryuQ==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/utils": "^6.1.3", + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.7", "prop-types": "^15.8.1" }, "engines": { @@ -1512,14 +1406,13 @@ } }, "node_modules/@mui/private-theming/node_modules/@mui/utils": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.3.tgz", - "integrity": "sha512-4JBpLkjprlKjN10DGb1aiy/ii9TKbQ601uSHtAmYFAS879QZgAD7vRnv/YBE4iBbc7NXzFgbQMCOFrupXWekIA==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/types": "^7.2.18", + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", "@types/prop-types": "^15.7.13", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1543,13 +1436,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.3.tgz", - "integrity": "sha512-i4yh9m+eMZE3cNERpDhVr6Wn73Yz6C7MH0eE2zZvw8d7EFkIJlCQNZd1xxGZqarD2DDq2qWHcjIOucWGhxACtA==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.7.tgz", + "integrity": "sha512-Ou4CxN7MQmwrfG1Pu6EYjPgPChQXxPDJrwgizLXlRPOad5qAq4gYXRuzrGQ2DfGjjwmJhjI8T6A0SeapAZPGig==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", + "@babel/runtime": "^7.26.0", "@emotion/cache": "^11.13.1", "@emotion/serialize": "^1.3.2", "@emotion/sheet": "^1.4.0", @@ -1578,17 +1470,16 @@ } }, "node_modules/@mui/system": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.3.tgz", - "integrity": "sha512-ILaD9UsLTBLjMcep3OumJMXh1PYr7aqnkHm/L47bH46+YmSL1zWAX6tWG8swEQROzW2GvYluEMp5FreoxOOC6w==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.7.tgz", + "integrity": "sha512-qbMGgcC/FodpuRSfjXlEDdbNQaW++eATh0vNBcPUv2/YXSpReoOpoT9FhogxEBNks+aQViDXBRZKh6HX2fVmwg==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/private-theming": "^6.1.3", - "@mui/styled-engine": "^6.1.3", - "@mui/types": "^7.2.18", - "@mui/utils": "^6.1.3", + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.7", + "@mui/styled-engine": "^6.1.7", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.7", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1619,14 +1510,13 @@ } }, "node_modules/@mui/system/node_modules/@mui/utils": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.3.tgz", - "integrity": "sha512-4JBpLkjprlKjN10DGb1aiy/ii9TKbQ601uSHtAmYFAS879QZgAD7vRnv/YBE4iBbc7NXzFgbQMCOFrupXWekIA==", - "license": "MIT", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/types": "^7.2.18", + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", "@types/prop-types": "^15.7.13", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1650,10 +1540,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.18", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", - "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", - "license": "MIT", + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1667,7 +1556,6 @@ "version": "5.16.6", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/types": "^7.2.15", @@ -1694,17 +1582,16 @@ } }, "node_modules/@mui/x-charts": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.20.0.tgz", - "integrity": "sha512-mm3ERanuxWWc16dYLC54jqQp1CrHFSvWYvaXvhaXhWZdNrSIWNEY4inCbrDuGvl+i7/uNJgxeINw4SOtQ/BOFA==", - "license": "MIT", + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.22.2.tgz", + "integrity": "sha512-0Y2du4Ed7gOT53l8vVJ4vKT+Jz4Dh/iHnLy8TtL3+XhbPH9Ndu9Q30WwyyzOn84yt37hSUru/njQ1BWaSvVPHw==", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-charts-vendor": "7.20.0", - "@mui/x-internals": "7.20.0", - "@react-spring/rafz": "^9.7.4", - "@react-spring/web": "^9.7.4", + "@mui/x-internals": "7.21.0", + "@react-spring/rafz": "^9.7.5", + "@react-spring/web": "^9.7.5", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -1732,7 +1619,6 @@ "version": "7.20.0", "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.20.0.tgz", "integrity": "sha512-pzlh7z/7KKs5o0Kk0oPcB+sY0+Dg7Q7RzqQowDQjpy5Slz6qqGsgOB5YUzn0L+2yRmvASc4Pe0914Ao3tMBogg==", - "license": "MIT AND ISC", "dependencies": { "@babel/runtime": "^7.25.7", "@types/d3-color": "^3.1.3", @@ -1752,14 +1638,13 @@ } }, "node_modules/@mui/x-data-grid": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.20.0.tgz", - "integrity": "sha512-B5tAbjfn5OPMbSJuwag17Eb6QTe70BgV414tWVCLz/bhJTovQ4C5aDGCY3ahyxZECsxcGDSTT/orWtFmKOUO5A==", - "license": "MIT", + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.22.2.tgz", + "integrity": "sha512-yfy2s5A6tbajQZiEdsba49T4FYb9F0WPrzbbG30dl1+sIiX4ZRX7ma44UIDGPZrsZv8xkkE+p8qeJxZ7OaMteA==", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.20.0", + "@mui/x-internals": "7.21.0", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.1.1" @@ -1789,10 +1674,9 @@ } }, "node_modules/@mui/x-internals": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.20.0.tgz", - "integrity": "sha512-ScXdEwtnxmBEq9umeusnotfeVQnnhjOZcM2ddXyIupmzeGmgDDtEcXGyTgrS/GOc91J74g81s6eJ4UCrlYZ2sg==", - "license": "MIT", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz", + "integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0" @@ -1813,7 +1697,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1827,7 +1710,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -1837,7 +1719,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1850,7 +1731,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1860,7 +1740,6 @@ "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", - "license": "MIT", "dependencies": { "@react-spring/shared": "~9.7.5", "@react-spring/types": "~9.7.5" @@ -1873,7 +1752,6 @@ "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", - "license": "MIT", "dependencies": { "@react-spring/animated": "~9.7.5", "@react-spring/shared": "~9.7.5", @@ -1890,14 +1768,12 @@ "node_modules/@react-spring/rafz": { "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", - "license": "MIT" + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==" }, "node_modules/@react-spring/shared": { "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", - "license": "MIT", "dependencies": { "@react-spring/rafz": "~9.7.5", "@react-spring/types": "~9.7.5" @@ -1909,14 +1785,12 @@ "node_modules/@react-spring/types": { "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", - "license": "MIT" + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==" }, "node_modules/@react-spring/web": { "version": "9.7.5", "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", - "license": "MIT", "dependencies": { "@react-spring/animated": "~9.7.5", "@react-spring/core": "~9.7.5", @@ -1929,224 +1803,234 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", + "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", + "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", + "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", + "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", + "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", + "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", + "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", + "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", + "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", + "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", + "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", + "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", + "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", + "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", + "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", + "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", + "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2157,7 +2041,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -2171,7 +2054,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } @@ -2181,7 +2063,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -2192,7 +2073,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } @@ -2200,20 +2080,17 @@ "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" }, "node_modules/@types/d3-delaunay": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", "dependencies": { "@types/d3-color": "*" } @@ -2221,14 +2098,12 @@ "node_modules/@types/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", - "license": "MIT" + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" }, "node_modules/@types/d3-scale": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", - "license": "MIT", "dependencies": { "@types/d3-time": "*" } @@ -2237,7 +2112,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", - "license": "MIT", "dependencies": { "@types/d3-path": "*" } @@ -2245,61 +2119,53 @@ "node_modules/@types/d3-time": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", - "license": "MIT" + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, - "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "license": "MIT" + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", - "license": "MIT", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/react": "*" } @@ -2308,24 +2174,22 @@ "version": "4.4.11", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", - "license": "MIT", "peer": true, "dependencies": { "@types/react": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", - "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", + "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/type-utils": "8.8.1", - "@typescript-eslint/utils": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/type-utils": "8.14.0", + "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2349,16 +2213,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", - "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", + "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "debug": "^4.3.4" }, "engines": { @@ -2378,14 +2241,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", + "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2396,14 +2258,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", - "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", + "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/utils": "8.14.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2421,11 +2282,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", + "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2435,14 +2295,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", + "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2468,7 +2327,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2478,7 +2336,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2494,7 +2351,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2503,16 +2359,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", - "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", + "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1" + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2526,13 +2381,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", + "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/types": "8.14.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2548,7 +2402,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2557,11 +2410,10 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", - "integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", @@ -2577,11 +2429,10 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2594,7 +2445,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -2604,7 +2454,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2617,29 +2466,30 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "dev": true }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -2654,15 +2504,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2673,7 +2521,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -2682,9 +2529,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -2700,12 +2547,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -2718,15 +2564,14 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001668", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", - "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "dev": true, "funding": [ { @@ -2741,74 +2586,65 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -2821,11 +2657,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2838,14 +2673,12 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", "dependencies": { "internmap": "1 - 2" }, @@ -2857,7 +2690,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -2866,7 +2698,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", "dependencies": { "delaunator": "5" }, @@ -2878,7 +2709,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -2887,7 +2717,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", "dependencies": { "d3-color": "1 - 3" }, @@ -2899,7 +2728,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", "engines": { "node": ">=12" } @@ -2908,7 +2736,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -2924,7 +2751,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", "dependencies": { "d3-path": "^3.1.0" }, @@ -2936,7 +2762,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", "dependencies": { "d3-array": "2 - 3" }, @@ -2948,7 +2773,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", "dependencies": { "d3-time": "1 - 3" }, @@ -2960,7 +2784,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2977,14 +2800,12 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" } @@ -2993,7 +2814,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", "peer": true, "dependencies": { "@babel/runtime": "^7.8.7", @@ -3001,17 +2821,15 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.36", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", - "integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", - "dev": true, - "license": "ISC" + "version": "1.5.59", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.59.tgz", + "integrity": "sha512-faAXB6+gEbC8FsiRdpOXgOe4snP49YwjiXynEB8Mp7sUx80W5eN+BnnBHJ/F7eIeLzs+QBfDD40bJMm97oEFcw==", + "dev": true }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -3022,7 +2840,6 @@ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -3060,7 +2877,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -3069,7 +2885,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -3078,22 +2893,21 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", + "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", + "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.14.0", "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", + "@humanwhocodes/retry": "^0.4.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -3101,9 +2915,9 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3143,7 +2957,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -3152,21 +2965,19 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", - "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", + "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", "dev": true, - "license": "MIT", "peerDependencies": { "eslint": ">=7" } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3179,11 +2990,10 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3191,92 +3001,15 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3290,7 +3023,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -3303,7 +3035,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3316,7 +3047,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3326,7 +3056,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3335,15 +3064,13 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3360,7 +3087,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -3372,22 +3098,19 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -3397,7 +3120,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -3410,7 +3132,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3421,15 +3142,13 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3446,7 +3165,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3459,8 +3177,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -3468,7 +3185,6 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3481,7 +3197,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3491,7 +3206,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -3501,7 +3215,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -3510,11 +3223,10 @@ } }, "node_modules/globals": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", - "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -3526,23 +3238,21 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -3554,7 +3264,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } @@ -3562,15 +3271,13 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -3579,7 +3286,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3596,7 +3302,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3605,7 +3310,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", "engines": { "node": ">=12" } @@ -3613,14 +3317,12 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -3636,7 +3338,6 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3646,7 +3347,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -3659,7 +3359,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3668,21 +3367,18 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3694,7 +3390,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -3706,35 +3401,30 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -3747,7 +3437,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -3757,7 +3446,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3769,15 +3457,13 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -3792,14 +3478,12 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -3812,7 +3496,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -3822,7 +3505,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -3832,7 +3514,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -3846,7 +3527,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3857,8 +3537,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -3871,7 +3550,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3883,21 +3561,18 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3907,7 +3582,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -3925,7 +3599,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3941,7 +3614,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -3956,7 +3628,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -3968,7 +3639,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -3987,7 +3657,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3997,7 +3666,6 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -4005,30 +3673,26 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "license": "ISC" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -4037,9 +3701,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -4055,10 +3719,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -4070,7 +3733,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -4079,7 +3741,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -4089,15 +3750,13 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -4120,14 +3779,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4139,7 +3796,6 @@ "version": "7.0.0-beta.47", "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-beta.47.tgz", "integrity": "sha512-28kjsmwQGD/9RXYC50zn5Zv/SQMhBBoSvG5seq0fM8XXi9TZ0zr9Z5T3YJqLwcEtoNzTOq3y0njkmdujGkIwQQ==", - "license": "MIT", "dependencies": { "clsx": "^2.0.0" }, @@ -4152,7 +3808,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4164,15 +3819,13 @@ "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4181,7 +3834,6 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", "peer": true, "dependencies": { "@babel/runtime": "^7.5.5", @@ -4197,20 +3849,17 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -4227,7 +3876,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", "engines": { "node": ">=4" } @@ -4237,7 +3885,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -4246,15 +3893,13 @@ "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", + "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.6" }, @@ -4266,22 +3911,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.26.0", + "@rollup/rollup-android-arm64": "4.26.0", + "@rollup/rollup-darwin-arm64": "4.26.0", + "@rollup/rollup-darwin-x64": "4.26.0", + "@rollup/rollup-freebsd-arm64": "4.26.0", + "@rollup/rollup-freebsd-x64": "4.26.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", + "@rollup/rollup-linux-arm-musleabihf": "4.26.0", + "@rollup/rollup-linux-arm64-gnu": "4.26.0", + "@rollup/rollup-linux-arm64-musl": "4.26.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", + "@rollup/rollup-linux-riscv64-gnu": "4.26.0", + "@rollup/rollup-linux-s390x-gnu": "4.26.0", + "@rollup/rollup-linux-x64-gnu": "4.26.0", + "@rollup/rollup-linux-x64-musl": "4.26.0", + "@rollup/rollup-win32-arm64-msvc": "4.26.0", + "@rollup/rollup-win32-ia32-msvc": "4.26.0", + "@rollup/rollup-win32-x64-msvc": "4.26.0", "fsevents": "~2.3.2" } }, @@ -4304,7 +3951,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -4313,7 +3959,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -4323,7 +3968,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -4333,7 +3977,6 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4346,7 +3989,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -4355,7 +3997,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4365,7 +4006,6 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4375,7 +4015,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -4386,26 +4025,24 @@ "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT" + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4417,24 +4054,13 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } + "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -4443,11 +4069,10 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=16" }, @@ -4460,7 +4085,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -4473,7 +4097,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4483,15 +4106,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.1.tgz", - "integrity": "sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.14.0.tgz", + "integrity": "sha512-K8fBJHxVL3kxMmwByvz8hNdBJ8a0YqKzKDX6jRlrjMuNXyd5T2V02HIq37+OiWXvUUOXgOOGiSSOh26Mh8pC3w==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.8.1", - "@typescript-eslint/parser": "8.8.1", - "@typescript-eslint/utils": "8.8.1" + "@typescript-eslint/eslint-plugin": "8.14.0", + "@typescript-eslint/parser": "8.14.0", + "@typescript-eslint/utils": "8.14.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4510,8 +4132,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/update-browserslist-db": { "version": "1.1.1", @@ -4532,7 +4153,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" @@ -4549,17 +4169,15 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -4619,7 +4237,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -4635,7 +4252,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4644,14 +4260,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", "engines": { "node": ">= 6" } @@ -4661,7 +4275,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -4669,5 +4282,2714 @@ "url": "https://github.com/sponsors/sindresorhus" } } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + } + }, + "@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true + }, + "@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "requires": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "requires": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + } + }, + "@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" + }, + "@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "requires": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + } + }, + "@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "requires": { + "@babel/types": "^7.26.0" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "requires": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + } + }, + "@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "requires": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + } + } + }, + "@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "requires": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + } + }, + "@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "requires": { + "@emotion/memoize": "^0.9.0" + } + }, + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "requires": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + } + }, + "@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true + }, + "@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true + }, + "@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "requires": { + "levn": "^0.4.1" + } + }, + "@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "requires": { + "@floating-ui/utils": "^0.2.8" + } + }, + "@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "requires": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "requires": { + "@floating-ui/dom": "^1.0.0" + } + }, + "@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, + "@fontsource/roboto": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.0.tgz", + "integrity": "sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==" + }, + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + } + }, + "@mui/core-downloads-tracker": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==" + }, + "@mui/icons-material": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.7.tgz", + "integrity": "sha512-RGzkeHNArIVy5ZQ12bq/8VYNeICEyngngsFskTJ/2hYKhIeIII3iRGtaZaSvLpXh7h3Fg3VKTulT+QU0w5K4XQ==", + "requires": { + "@babel/runtime": "^7.26.0" + } + }, + "@mui/joy": { + "version": "5.0.0-beta.48", + "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.48.tgz", + "integrity": "sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.16.1", + "@mui/system": "^5.16.1", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.1", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "dependencies": { + "@mui/private-theming": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.6", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "requires": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + } + } + } + }, + "@mui/material": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.7.tgz", + "integrity": "sha512-KsjujQL/A2hLd1PV3QboF+W6SSL5QqH6ZlSuQoeYz9r69+TnyBFIevbYLxdjJcJmGBjigL5pfpn7hTGop+vhSg==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.7", + "@mui/system": "^6.1.7", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.7", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@mui/core-downloads-tracker": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.7.tgz", + "integrity": "sha512-POuIBi80BZBogQkG4PQKIGwy4QFwB+kOr+OI4k7Znh7LqMAIhwB9OC00l6M+w1GrZJYj3T8R5WX8G6QAIvoVEw==", + "peer": true + }, + "@mui/utils": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + } + } + } + }, + "@mui/private-theming": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.7.tgz", + "integrity": "sha512-uLbfUSsug5K0LVkv0PI6Flste3le8+6WSL2omdTiYde93P89Qr7pKr8TA6d2yXfr+Bm+SvD8/fGnkaRwFkryuQ==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.7", + "prop-types": "^15.8.1" + }, + "dependencies": { + "@mui/utils": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + } + } + } + }, + "@mui/styled-engine": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.7.tgz", + "integrity": "sha512-Ou4CxN7MQmwrfG1Pu6EYjPgPChQXxPDJrwgizLXlRPOad5qAq4gYXRuzrGQ2DfGjjwmJhjI8T6A0SeapAZPGig==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.7.tgz", + "integrity": "sha512-qbMGgcC/FodpuRSfjXlEDdbNQaW++eATh0vNBcPUv2/YXSpReoOpoT9FhogxEBNks+aQViDXBRZKh6HX2fVmwg==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.7", + "@mui/styled-engine": "^6.1.7", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.7", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "dependencies": { + "@mui/utils": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.7.tgz", + "integrity": "sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==", + "peer": true, + "requires": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + } + } + } + }, + "@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "requires": {} + }, + "@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + } + }, + "@mui/x-charts": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.22.2.tgz", + "integrity": "sha512-0Y2du4Ed7gOT53l8vVJ4vKT+Jz4Dh/iHnLy8TtL3+XhbPH9Ndu9Q30WwyyzOn84yt37hSUru/njQ1BWaSvVPHw==", + "requires": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-charts-vendor": "7.20.0", + "@mui/x-internals": "7.21.0", + "@react-spring/rafz": "^9.7.5", + "@react-spring/web": "^9.7.5", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + } + }, + "@mui/x-charts-vendor": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.20.0.tgz", + "integrity": "sha512-pzlh7z/7KKs5o0Kk0oPcB+sY0+Dg7Q7RzqQowDQjpy5Slz6qqGsgOB5YUzn0L+2yRmvASc4Pe0914Ao3tMBogg==", + "requires": { + "@babel/runtime": "^7.25.7", + "@types/d3-color": "^3.1.3", + "@types/d3-delaunay": "^6.0.4", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-scale": "^4.0.8", + "@types/d3-shape": "^3.1.6", + "@types/d3-time": "^3.0.3", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "delaunator": "^5.0.1", + "robust-predicates": "^3.0.2" + } + }, + "@mui/x-data-grid": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.22.2.tgz", + "integrity": "sha512-yfy2s5A6tbajQZiEdsba49T4FYb9F0WPrzbbG30dl1+sIiX4ZRX7ma44UIDGPZrsZv8xkkE+p8qeJxZ7OaMteA==", + "requires": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-internals": "7.21.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1" + } + }, + "@mui/x-internals": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz", + "integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==", + "requires": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, + "@react-spring/animated": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", + "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", + "requires": { + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + } + }, + "@react-spring/core": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", + "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", + "requires": { + "@react-spring/animated": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + } + }, + "@react-spring/rafz": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==" + }, + "@react-spring/shared": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", + "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", + "requires": { + "@react-spring/rafz": "~9.7.5", + "@react-spring/types": "~9.7.5" + } + }, + "@react-spring/types": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==" + }, + "@react-spring/web": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", + "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", + "requires": { + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", + "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", + "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", + "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", + "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", + "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", + "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", + "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", + "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", + "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", + "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", + "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", + "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", + "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", + "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", + "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", + "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", + "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", + "dev": true, + "optional": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dev": true, + "requires": { + "undici-types": "~6.19.8" + } + }, + "@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" + }, + "@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "peer": true, + "requires": { + "@types/react": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", + "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/type-utils": "8.14.0", + "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/parser": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", + "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", + "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", + "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/utils": "8.14.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/types": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", + "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", + "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", + "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", + "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.14.0", + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@vitejs/plugin-react": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "dev": true, + "requires": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + } + }, + "acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite": { + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "requires": { + "delaunator": "5" + } + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "requires": { + "robust-predicates": "^3.0.2" + } + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "peer": true, + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "electron-to-chromium": { + "version": "1.5.59", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.59.tgz", + "integrity": "sha512-faAXB6+gEbC8FsiRdpOXgOe4snP49YwjiXynEB8Mp7sUx80W5eN+BnnBHJ/F7eIeLzs+QBfDD40bJMm97oEFcw==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", + "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.14.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.0", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + } + }, + "eslint-plugin-react-hooks": { + "version": "5.1.0-rc-fb9a90fa48-20240614", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", + "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "dev": true, + "requires": {} + }, + "eslint-plugin-react-refresh": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", + "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "requires": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + } + }, + "esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "requires": { + "flat-cache": "^4.0.0" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + } + }, + "flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "requires": { + "hasown": "^2.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-data-grid": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-beta.47.tgz", + "integrity": "sha512-28kjsmwQGD/9RXYC50zn5Zv/SQMhBBoSvG5seq0fM8XXi9TZ0zr9Z5T3YJqLwcEtoNzTOq3y0njkmdujGkIwQQ==", + "requires": { + "clsx": "^2.0.0" + } + }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true + }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "peer": true, + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "rollup": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", + "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.26.0", + "@rollup/rollup-android-arm64": "4.26.0", + "@rollup/rollup-darwin-arm64": "4.26.0", + "@rollup/rollup-darwin-x64": "4.26.0", + "@rollup/rollup-freebsd-arm64": "4.26.0", + "@rollup/rollup-freebsd-x64": "4.26.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", + "@rollup/rollup-linux-arm-musleabihf": "4.26.0", + "@rollup/rollup-linux-arm64-gnu": "4.26.0", + "@rollup/rollup-linux-arm64-musl": "4.26.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", + "@rollup/rollup-linux-riscv64-gnu": "4.26.0", + "@rollup/rollup-linux-s390x-gnu": "4.26.0", + "@rollup/rollup-linux-x64-gnu": "4.26.0", + "@rollup/rollup-linux-x64-musl": "4.26.0", + "@rollup/rollup-win32-arm64-msvc": "4.26.0", + "@rollup/rollup-win32-ia32-msvc": "4.26.0", + "@rollup/rollup-win32-x64-msvc": "4.26.0", + "@types/estree": "1.0.6", + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-api-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "dev": true, + "requires": {} + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true + }, + "typescript-eslint": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.14.0.tgz", + "integrity": "sha512-K8fBJHxVL3kxMmwByvz8hNdBJ8a0YqKzKDX6jRlrjMuNXyd5T2V02HIq37+OiWXvUUOXgOOGiSSOh26Mh8pC3w==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "8.14.0", + "@typescript-eslint/parser": "8.14.0", + "@typescript-eslint/utils": "8.14.0" + } + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "requires": { + "esbuild": "^0.21.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } } } diff --git a/ui/frontend/src/App.tsx b/ui/frontend/src/App.tsx index a8853e6..dc891fa 100644 --- a/ui/frontend/src/App.tsx +++ b/ui/frontend/src/App.tsx @@ -219,14 +219,15 @@ const ColoredCell = ({ params, type, metric }: { params: GridCellParams, type: s let textColor = 'black'; if (0 != currentValue) { - const minValue = Math.min(...nonEmptyValues); - const maxValue = Math.max(...nonEmptyValues); - + const logMinValue = Math.log(Math.min(...nonEmptyValues)); + const logMaxValue = Math.log(Math.max(...nonEmptyValues)); + const logCurrentValue = Math.log(currentValue); + // The color changes from green (0, 255, 0) to yellow (255, 255, 0) to red (255, 0, 0) to dark (0, 0, 0). // So there are three stages. - const ratio = reverse ? (maxValue - currentValue) / (maxValue - minValue) - : (currentValue - minValue) / (maxValue - minValue); + const ratio = reverse ? (logMaxValue - logCurrentValue) / (logMaxValue - logMinValue) + : (logCurrentValue - logMinValue) / (logMaxValue - logMinValue); if (0.3333 > ratio) { // Change from green (0, 255, 0) to yellow (255, 255, 0) redValue = 255 * ratio / 0.3333; @@ -236,8 +237,8 @@ const ColoredCell = ({ params, type, metric }: { params: GridCellParams, type: s redValue = 255; greenValue = 255 * (1 - (ratio - 0.3333) / 0.3333); } else { - // Change from red to dark (255, 0, 0) to dark (0, 0, 0) - redValue = 255 * (1 - (ratio - 0.6666) / 0.3333); + // Change from red to dark (255, 0, 0) to dark red (128, 0, 0) + redValue = 64 + 192 * (1 - (ratio - 0.6666) / 0.3333); greenValue = 0 }