From 66117ad6f7e48062dc041ad985e4fe573ff77e92 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Sat, 9 Mar 2024 18:38:28 +0800 Subject: [PATCH 1/6] chore: fix quick start Signed-off-by: iGxnon --- USAGE.md | 2 +- doc/quick-start/README.md | 2 +- scripts/benchmark.sh | 4 ++-- scripts/log.sh | 2 +- scripts/prometheus.yml | 7 +++---- scripts/quick_start.sh | 23 ++++++++++++----------- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/USAGE.md b/USAGE.md index c7369bf02..e2f76fcc0 100644 --- a/USAGE.md +++ b/USAGE.md @@ -65,7 +65,7 @@ retry_timeout = '50ms' # the rpc retry interval, of which the default i 2. Use the following command to start cluster: ```bash - # Run in 3 terminals. If you want more logs, add `RUST_LOG=debug` before the command. + # Run in 3 terminals. If you want more logs, add `RUST_LOG=curp=debug,xline=debug` before the command. ./xline --name node1 --members node1=127.0.0.1:2379,node2=127.0.0.1:2380,node3=127.0.0.1:2381 --is-leader diff --git a/doc/quick-start/README.md b/doc/quick-start/README.md index 6ddf13de9..795a42319 100644 --- a/doc/quick-start/README.md +++ b/doc/quick-start/README.md @@ -127,7 +127,7 @@ ETCD_INITIAL_CLUSTER_STATE="existing" # boot up a new node $ docker run -d -it --rm --name=node4 --net=xline_net --ip=172.20.0.6 --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v /home/jiawei/Xline/scripts:/mnt ghcr.io/xline-kv/xline:latest bash -$ docker exec -e RUST_LOG=debug -d node4 "/usr/local/bin/xline --name node4 --members node1=172.20.0.3:2379,172.20.0.3:2380,node2=172.20.0.4:2379,172.20.0.4:2380,node3=172.20.0.5:2379,172.20.0.5:2380,node4=172.20.0.6:2379,172.20.0.6:2380 --storage-engine rocksdb --data-dir /usr/local/xline/data-dir --auth-public-key /mnt/public.pem --auth-private-key /mnt/private.pem --initial-cluster-state=existing" +$ docker exec -d node4 "/usr/local/bin/xline --name node4 --members node1=172.20.0.3:2379,172.20.0.3:2380,node2=172.20.0.4:2379,172.20.0.4:2380,node3=172.20.0.5:2379,172.20.0.5:2380,node4=172.20.0.6:2379,172.20.0.6:2380 --storage-engine rocksdb --data-dir /usr/local/xline/data-dir --auth-public-key /mnt/public.pem --auth-private-key /mnt/private.pem --initial-cluster-state=existing" # check whether the new member adding success or not $ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member list -w table" diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 8bd96a300..00c8621f9 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -61,8 +61,8 @@ run_xline() { cmd="${cmd} --is-leader" fi - docker exec -e RUST_LOG=curp,xline. -d node${1} ${cmd} - echo "docker exec -e RUST_LOG=curp,xline -d node${1} ${cmd}" + docker exec -e RUST_LOG=curp=debug,xline=debug -d node${1} ${cmd} + echo "docker exec -e RUST_LOG=curp=debug,xline=debug -d node${1} ${cmd}" } # run etcd node by index diff --git a/scripts/log.sh b/scripts/log.sh index 9afc534cb..b9f9a728d 100644 --- a/scripts/log.sh +++ b/scripts/log.sh @@ -1,4 +1,4 @@ -${__E2E_COMMON_LOG__:=false} && return 0 || __E2E_COMMON_LOG__=true +${__LOG__:=false} && return 0 || __LOG__=true function log::debug() { echo -e "\033[00;34m" "[DEBUG]" "$@" "\033[0m" diff --git a/scripts/prometheus.yml b/scripts/prometheus.yml index c3c5bd9c8..05b96c8a2 100644 --- a/scripts/prometheus.yml +++ b/scripts/prometheus.yml @@ -6,10 +6,9 @@ scrape_configs: static_configs: - targets: [ - "172.20.0.2:2379", - "172.20.0.3:2379", - "172.20.0.4:2379", - "172.20.0.5:2379", + "node1:9100", + "node2:9100", + "node3:9100", ] metrics_path: /metrics scheme: http diff --git a/scripts/quick_start.sh b/scripts/quick_start.sh index 1185bdbf2..3a0652ad0 100755 --- a/scripts/quick_start.sh +++ b/scripts/quick_start.sh @@ -1,10 +1,12 @@ #!/bin/bash + DIR=$( cd "$(dirname "$0")" pwd ) SERVERS=("172.20.0.2" "172.20.0.3" "172.20.0.4" "172.20.0.5") MEMBERS="node1=${SERVERS[1]}:2380,${SERVERS[1]}:2381,node2=${SERVERS[2]}:2380,${SERVERS[2]}:2381,node3=${SERVERS[3]}:2380,${SERVERS[3]}:2381" +LOG_PATH=${LOG_PATH:-"/mnt"} # default log to /mnt (i.e. the scripts directory) source $DIR/log.sh @@ -14,11 +16,12 @@ stop_all() { for name in "node1" "node2" "node3" "client"; do docker_id=$(docker ps -qf "name=${name}") if [ -n "$docker_id" ]; then - docker stop $docker_id + docker exec $docker_id rm -rf $LOG_PATH/$name + docker stop $docker_id -t 1 fi done docker network rm xline_net >/dev/null 2>&1 - docker stop "prometheus" + docker stop "prometheus" > /dev/null 2>&1 sleep 1 log::info stopped } @@ -37,18 +40,16 @@ run_xline() { --client-listen-urls=http://${SERVERS[$1]}:2379 \ --peer-listen-urls=http://${SERVERS[$1]}:2380,http://${SERVERS[$1]}:2381 \ --client-advertise-urls=http://${SERVERS[$1]}:2379 \ - --peer-advertise-urls=http://${SERVERS[$1]}:2380,http://${SERVERS[$1]}:2381" - - if [ -n "$LOG_LEVEL" ]; then - cmd="${cmd} --log-level ${LOG_LEVEL}" - fi + --peer-advertise-urls=http://${SERVERS[$1]}:2380,http://${SERVERS[$1]}:2381 \ + --log-file ${LOG_PATH}/node${1} --log-level debug" if [ ${1} -eq 1 ]; then cmd="${cmd} --is-leader" fi - docker exec -e RUST_LOG=debug -d node${1} ${cmd} - log::info "command is: docker exec -e RUST_LOG=debug -d node${1} ${cmd}" + exec="docker exec -e RUST_LOG=curp=debug,xline=debug -d node${1} ${cmd}" + eval $exec + log::info "start node${1} with command: ${exec}" } # run cluster of xline/etcd in container @@ -80,7 +81,7 @@ run_container() { done docker run -d -it --rm --name=client \ --net=xline_net --ip=${SERVERS[0]} --cap-add=NET_ADMIN \ - --cpu-shares=1024 -m=512M -v ${DIR}:/mnt ghcr.io/xline-kv/etcdctl:v3.5.9 bash & + --cpu-shares=1024 -m=512M -v ${DIR}:/mnt ghcr.io/xline-kv/etcdctl:v3.5.9 tail -F /dev/null wait log::info container started } @@ -99,7 +100,7 @@ if [ -z "$1" ]; then run_container 3 run_cluster run_prometheus "172.20.0.6" - echo "Prometheus starts on http://172.20.0.6:9090/graph and http://127.0.0.1:9090/graph" + echo "Prometheus starts on http://172.20.0.6:9090/graph and http://127.0.0.1:9090/graph (if you are using Docker Desktop)." exit 0 elif [ "$1" == "stop" ]; then stop_all From dc03a8cfee1b8f57fb47adf184df569454ea1571 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Sat, 9 Mar 2024 20:01:30 +0800 Subject: [PATCH 2/6] chore: tidy quick start & fix benchmark Signed-off-by: iGxnon --- README.md | 2 +- doc/QUICK_START.md | 140 ++++++++++++++++++++++++++++ doc/img/prom_demo.png | Bin 0 -> 85578 bytes doc/metrics.md | 113 +++++++++++++++++++++++ doc/quick-start/Dockerfile | 25 ----- doc/quick-start/README.md | 181 ------------------------------------- scripts/benchmark.sh | 17 +++- 7 files changed, 266 insertions(+), 212 deletions(-) create mode 100644 doc/QUICK_START.md create mode 100644 doc/img/prom_demo.png create mode 100644 doc/metrics.md delete mode 100644 doc/quick-start/Dockerfile delete mode 100644 doc/quick-start/README.md diff --git a/README.md b/README.md index 06d7b11f3..45a76e5ca 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ For more information about the Xline client SDK, or the Xline client command lin ## Quick Start -To get started, check out the document [QUICK_START.md](doc/quick-start/README.md) for in-depth information and step-by-step instructions. +To get started, check out the document [QUICK_START.md](doc/QUICK_START.md) for in-depth information and step-by-step instructions. ## Contribute Guide diff --git a/doc/QUICK_START.md b/doc/QUICK_START.md new file mode 100644 index 000000000..bf0a8cdfb --- /dev/null +++ b/doc/QUICK_START.md @@ -0,0 +1,140 @@ +# Quick Start + +## Single node cluster + +### Using docker + +```bash +# Assume that docker engine environment is installed. + +$ docker run -it --rm --name=xline -e RUST_LOG=xline=debug -p 2379:2379 ghcr.io/xline-kv/xline \ + xline \ + --name xline \ + --storage-engine rocksdb \ + --members xline=127.0.0.1:2379 \ + --data-dir /usr/local/xline/data-dir \ + --client-listen-urls http://0.0.0.0:2379 \ + --peer-listen-urls http://0.0.0.0:2380 \ + --client-advertise-urls http://127.0.0.1:2379 \ + --peer-advertise-urls http://127.0.0.1:2380 +``` + +```bash +# Try with etcdctl + +$ ETCDCTL_API=3 etcdctl put A 1 +OK +$ ETCDCTL_API=3 etcdctl get A +A +1 +``` + +### Build from source + +1. Install dependencies + +```bash +# Ubuntu/Debian + +$ sudo apt-get install -y autoconf autogen libtool + +# Requires protobuf-compiler >= 3.15 +$ git clone --branch v3.21.12 --recurse-submodules https://github.com/protocolbuffers/protobuf +$ cd protobuf +$ ./autogen.sh +$ ./configure +$ make -j$(nproc) +$ sudo make install +``` + +```bash +# macOS + +# Assume that brew is installed, or you could install brew by: +# /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +$ brew install protobuf +``` + +2. Build xline + +```bash +# Assume that rust compile environment installed, such as cargo, etc. + +# clone source code +$ git clone --recurse-submodules https://github.com/xline-kv/Xline + +# compile Xline +$ cd Xline +$ cargo build --release +``` + +3. Run xline + +```bash +$ ./target/release/xline --name xline \ + --storage-engine rocksdb \ + --members xline=127.0.0.1:2379 \ + --data-dir \ + --client-listen-urls http://0.0.0.0:2379 \ + --peer-listen-urls http://0.0.0.0:2380 \ + --client-advertise-urls http://127.0.0.1:2379 \ + --peer-advertise-urls http://127.0.0.1:2380 +``` + +## Standard xline cluster + +1. Start the cluster + +```bash +# Pull the latest image from ghcr.io +$ docker pull ghcr.io/xline-kv/xline:latest +# Copy some fixtures which are required by quick_start.sh +$ cp fixtures/{private,public}.pem scripts/ +# Using the quick start scripts +$ ./scripts/quick_start.sh +``` + +2. Basic requests + +```bash +# Set Key A's value to 1 +$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://node1:2379\" put A 1" +OK + +# Get Key A's value +$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://node1:2379\" get A" +A +1 +``` + +3. Inspect metrics + +After finished `Start the cluster`, you can goto http://127.0.0.1:9090/graph. +You should be able to see a web ui of Prometheus. + +For example: + +This means the `node1` is the leader. + +![](./img/prom_demo.png) + +For more metrics, please goto [metrics.md](./metrics.md) + +4. Benchmark + +```bash +$ ./scripts/quick_start.sh stop +$ ./scripts/benchmark.sh xline +``` + +## Directory Structure + +| directory name | description | +|----------------|---------------------------------------------------------| +| benchmark | a customized benchmark using CURP protocol based client | +| curp | the CURP protocol | +| xline | xline services | +| engine | persistent storage | +| utils | some utilities, like lock, config, etc. | +| scripts | the shell scripts for env deployment or benchmarking | diff --git a/doc/img/prom_demo.png b/doc/img/prom_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..4d253f02ee6ded175f47ecf9dea08e2b23b480b1 GIT binary patch literal 85578 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�V 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 literal 0 HcmV?d00001 diff --git a/doc/metrics.md b/doc/metrics.md new file mode 100644 index 000000000..42c40cb2a --- /dev/null +++ b/doc/metrics.md @@ -0,0 +1,113 @@ +## Xline metrics + +Many metrics are similar to those in [etcd](https://etcd.io/docs/v3.5/metrics/). + +### CURP Server + +1. `leader_changes`: Counter +The number of leader changes seen. + +2. `learner_promote_failed`: Counter +The total number of failed learner promotions (likely learner not ready) while this member is leader. + +3. `learner_promote_succeed`: Counter +The total number of successful learner promotions while this member is leader. + +4. `heartbeat_send_failures`: Counter +The total number of leader heartbeat send failures (likely overloaded from slow disk). + +5. `apply_snapshot_in_progress`: ObservableGauge +1 if the server is applying the incoming snapshot. 0 if none. + +6. `proposals_committed`: ObservableGauge +The total number of consensus proposals committed. + +7. `proposals_failed`: Counter +The total number of failed proposals seen. + +8. `proposals_applied`: ObservableGauge +The total number of consensus proposals applied. + +9. `proposals_pending`: ObservableGauge +The current number of pending proposals to commit. + +10. `snapshot_install_total_duration_seconds`: Histogram +The total latency distributions of save called by install_snapshot. + +11. `client_id_revokes`: Counter +The total number of client id revokes times. + +12. `has_leader`: ObservableGauge +Whether or not a leader exists. 1 is existence, 0 is not. + +13. `is_leader`: ObservableGauge +Whether or not this member is a leader. 1 if is, 0 otherwise. + +14. `is_learner`: ObservableGauge +Whether or not this member is a learner. 1 if is, 0 otherwise. + +15. `server_id`: ObservableGauge +Server or member ID in hexadecimal format. 1 for 'server_id' label with the current ID. + +16. `sp_total`: ObservableGauge +The speculative pool size of this server. + +17. `online_clients`: ObservableGauge +The online client IDs count of this server if it is the leader. + +### CURP Client + +1. `client_retry_count`: Counter +The total number of retries when the client propose to the cluster. + +2. `client_fast_path_count`: Counter +The total number of fast path when the client propose to the cluster. + +3. `client_slow_path_count`: Counter +The total number of slow path when the client propose to the cluster. + +4. `client_fast_path_fallback_slow_path_count`: Counter +The total number of fast path fallbacks into slow path when the client propose to the cluster. + +### Xline + +1. `slow_read_indexes_total`: Counter +The total number of pending read indexes not in sync with leader's or timed out read index requests. + +2. `read_indexes_failed_total`: Counter +The total number of failed read indexes seen. + +3. `lease_expired_total`: Counter +The total number of expired leases. + +4. `fd_used`: ObservableGauge +The number of used file descriptors. + +5. `fd_limit`: ObservableGauge +The file descriptor limit. + +6. `current_version`: ObservableGauge +Which version is running. 1 for 'server_version' label with the current version. + +7. `current_rust_version`: ObservableGauge +Which Rust version the server is running with. 1 for 'server_rust_version' label with the current version. + + +### Engine + +1. `engine_apply_snapshot_duration_seconds`: Histogram +The backend engine apply snapshot duration in seconds. + +2. `engine_write_batch_duration_seconds`: Histogram +The backend engine write batch engine, `batch_size` refer to the size and `sync` if sync option is on. + +### Network + +1. `peer_sent_bytes_total`: Counter +The total number of bytes sent to peers. + +2. `peer_sent_failures_total`: Counter +The total number of send failures to peers. + +3. `peer_round_trip_time_seconds`: Histogram +The round-trip-time histogram between peers. diff --git a/doc/quick-start/Dockerfile b/doc/quick-start/Dockerfile deleted file mode 100644 index e82b1c669..000000000 --- a/doc/quick-start/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM rust:1.67.1-slim-bullseye as builder - -WORKDIR /build - -COPY . . - -RUN set -eux && \ - sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ - apt-get update && apt-get install -y git build-essential autoconf autogen libtool clang && \ - git clone --branch v3.21.12 --recurse-submodules https://github.com/protocolbuffers/protobuf && \ - cd protobuf && ./autogen.sh && ./configure && make -j4 && make install && cd .. && ldconfig && \ - cargo build --release - -FROM debian:bullseye-slim - -RUN set -eux && \ - apt-get update && \ - apt-get install -y iproute2 iputils-ping procps && \ - rm -rf /var/lib/apt/lists/* - -COPY --from=builder /build/target/release/xline /usr/local/bin -COPY --from=builder /build/target/release/benchmark /usr/local/bin -COPY --from=builder /build/target/release/validation_lock_client /usr/local/bin - -CMD ["/usr/local/bin/xline"] diff --git a/doc/quick-start/README.md b/doc/quick-start/README.md deleted file mode 100644 index 795a42319..000000000 --- a/doc/quick-start/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# Quick Start - -## Run Xline from a pre-built image - -```bash -# Assume that docker engine environment is installed. - -docker run -it --name=xline ghcr.io/xline-kv/xline \ - xline \ - --name xline \ - --storage-engine rocksdb \ - --members xline=127.0.0.1:2379 \ - --data-dir /usr/local/xline/data-dir -``` - -## Run Xline from source code - -### Install dependencies - -#### Ubuntu/Debian - -```bash -sudo apt-get install -y autoconf autogen libtool - -# requires protobuf-compiler >= 3.15 -git clone --branch v3.21.12 --recurse-submodules https://github.com/protocolbuffers/protobuf -cd protobuf -./autogen.sh -./configure -make -j -sudo make install -``` - -#### macOS - -```bash -# Assume that brew is installed, or you could install brew by: -# /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - -brew install protobuf -``` - -### Build Xline from source - -```bash -# Assume that rust compile environment installed, such as cargo, etc. - -# clone source code -git clone --recurse-submodules https://github.com/datenlord/Xline - -# compile Xline -cd Xline -cargo build --release -``` - -### Run Xline - -```bash -./target/release/xline --name xline \ - --storage-engine rocksdb \ - --members xline=127.0.0.1:2379 \ - --data-dir -``` - -## Test Xline cluster - -### Pull or Build image for validation - -#### Pull the latest image from ghcr.io -```bash -# Assume that docker engine environment is installed. - docker pull ghcr.io/xline-kv/xline:latest - ``` - -#### Build image -```bash -# Assume that docker engine environment is installed. - -# clone source code -git clone --recurse-submodules https://github.com/datenlord/Xline -cd Xline - -# build docker image -# you may need to add sudo before the command to make it work -docker build . -t ghcr.io/xline-kv/xline -f doc/quick-start/Dockerfile -``` - -### Start Xline servers - -```bash -cp ./fixtures/{private,public}.pem ./scripts - -./scripts/quick_start.sh -``` - -### Test basic etcd requests - -```bash -# Set Key A's value to 1 -docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" put A 1" - -# Get Key A's value -docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" get A" -``` - -### Membership Change -```bash -# Before member add -$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member list -w table" -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ -| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ -| c446f1764cf82129 | started | node3 | 172.20.0.5:2379,172.20.0.5:2380 | 172.20.0.5:2379,172.20.0.5:2380 | false | -| 536070dcd739623d | started | node1 | 172.20.0.3:2379,172.20.0.3:2380 | 172.20.0.3:2379,172.20.0.3:2380 | false | -| c58a7f879100c944 | started | node2 | 172.20.0.4:2379,172.20.0.4:2380 | 172.20.0.4:2379,172.20.0.4:2380 | false | -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ - -# do the member add -$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member add node4 --peer-urls=http://172.20.0.6:2379,http://172.20.0.6:2380" -Member 7bcbb7db4adc6890 added to cluster 73917cf4cbc75001 - -ETCD_NAME="node4" -ETCD_INITIAL_CLUSTER="node4=http://172.20.0.6:2379,node4=http://172.20.0.6:2380,node2=172.20.0.4:2379,node2=172.20.0.4:2380,node3=172.20.0.5:2379,node3=172.20.0.5:2380,node1=172.20.0.3:2379,node1=172.20.0.3:2380" -ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.20.0.6:2379,http://172.20.0.6:2380" -ETCD_INITIAL_CLUSTER_STATE="existing" - -# boot up a new node -$ docker run -d -it --rm --name=node4 --net=xline_net --ip=172.20.0.6 --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v /home/jiawei/Xline/scripts:/mnt ghcr.io/xline-kv/xline:latest bash - -$ docker exec -d node4 "/usr/local/bin/xline --name node4 --members node1=172.20.0.3:2379,172.20.0.3:2380,node2=172.20.0.4:2379,172.20.0.4:2380,node3=172.20.0.5:2379,172.20.0.5:2380,node4=172.20.0.6:2379,172.20.0.6:2380 --storage-engine rocksdb --data-dir /usr/local/xline/data-dir --auth-public-key /mnt/public.pem --auth-private-key /mnt/private.pem --initial-cluster-state=existing" - -# check whether the new member adding success or not -$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member list -w table" -+------------------+---------+-------+-----------------------------------------------+-----------------------------------------------+------------+ -| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | -+------------------+---------+-------+-----------------------------------------------+-----------------------------------------------+------------+ -| 7bcbb7db4adc6890 | started | node4 | http://172.20.0.6:2379,http://172.20.0.6:2380 | http://172.20.0.6:2379,http://172.20.0.6:2380 | false | -| c58a7f879100c944 | started | node2 | 172.20.0.4:2379,172.20.0.4:2380 | 172.20.0.4:2379,172.20.0.4:2380 | false | -| c446f1764cf82129 | started | node3 | 172.20.0.5:2379,172.20.0.5:2380 | 172.20.0.5:2379,172.20.0.5:2380 | false | -| 536070dcd739623d | started | node1 | 172.20.0.3:2379,172.20.0.3:2380 | 172.20.0.3:2379,172.20.0.3:2380 | false | -+------------------+---------+-------+-----------------------------------------------+-----------------------------------------------+------------+ - -# do the member remove -$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member remove 7bcbb7db4adc6890" -Member 7bcbb7db4adc6890 removed from cluster 73917cf4cbc75001 - -# check whether the target member removed success or not -$ docker exec client /bin/sh -c "/usr/local/bin/etcdctl --endpoints=\"http://172.20.0.3:2379\" member list -w table" -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ -| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ -| c58a7f879100c944 | started | node2 | 172.20.0.4:2379,172.20.0.4:2380 | 172.20.0.4:2379,172.20.0.4:2380 | false | -| c446f1764cf82129 | started | node3 | 172.20.0.5:2379,172.20.0.5:2380 | 172.20.0.5:2379,172.20.0.5:2380 | false | -| 536070dcd739623d | started | node1 | 172.20.0.3:2379,172.20.0.3:2380 | 172.20.0.3:2379,172.20.0.3:2380 | false | -+------------------+---------+-------+---------------------------------+---------------------------------+------------+ -``` - -### Validation test - -```bash -docker cp node1:/usr/local/bin/lock_client ./scripts - -./scripts/validation_test.sh -``` - -### Benchmark - -```bash -./scripts/benchmark.sh -``` - -# Directory Structure - -| directory name | description | -|----------------|---------------------------------------------------------| -| benchmark | a customized benchmark using CURP protocol based client | -| curp | the CURP protocol | -| xline | xline services | -| engine | persistent storage | -| utils | some utilities, like lock, config, etc. | -| scripts | the shell scripts for env deployment or benchmarking | diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 00c8621f9..fc1c16866 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -2,7 +2,7 @@ WORKDIR=$(pwd) OUTPUT_DIR="${WORKDIR}/out" SERVERS=("172.20.0.2" "172.20.0.3" "172.20.0.4" "172.20.0.5") -MEMBERS="node1=${SERVERS[1]}:2379,node2=${SERVERS[2]}:2379,node3=${SERVERS[3]}:2379" +MEMBERS="node1=${SERVERS[1]}:2380,node2=${SERVERS[2]}:2380,node3=${SERVERS[3]}:2380" # container use_curp endpoints # XLINE_TESTCASE[0] VS ETCD_TESTCASE[0]: In the best performance case contrast, xline uses the curp-client while the @@ -55,7 +55,11 @@ run_xline() { --client-wait-synced-timeout 10s \ --client-propose-timeout 5s \ --batch-timeout 1ms \ - --cmd-workers 16" + --cmd-workers 16 \ + --client-listen-urls=http://${SERVERS[$1]}:2379 \ + --peer-listen-urls=http://${SERVERS[$1]}:2380 \ + --client-advertise-urls=http://${SERVERS[$1]}:2379 \ + --peer-advertise-urls=http://${SERVERS[$1]}:2380" if [ ${1} -eq 1 ]; then cmd="${cmd} --is-leader" @@ -127,7 +131,7 @@ stop_all() { for name in "node1" "node2" "node3" "client"; do docker_id=$(docker ps -qf "name=${name}") if [ -n "$docker_id" ]; then - docker stop $docker_id + docker stop $docker_id -t 1 fi done sleep 1 @@ -216,7 +220,7 @@ rm -r ${OUTPUT_DIR} >/dev/null 2>&1 mkdir ${OUTPUT_DIR} mkdir ${OUTPUT_DIR}/logs -for server in "xline" "etcd"; do +for server in $@; do count=0 logs_dir=${OUTPUT_DIR}/logs/${server}_logs mkdir -p ${logs_dir} @@ -229,11 +233,14 @@ for server in "xline" "etcd"; do etcd) TESTCASE=("${ETCD_TESTCASE[@]}") ;; + *) + echo "unknown server, only support xline/etcd" + exit 1 + ;; esac run_container 3 ${server} for testcase in "${TESTCASE[@]}"; do - tmp=(${testcase}) container_name=${tmp[0]} case ${tmp[1]} in From f13ca59a723803b9cc3ad9ea6b8c5e20617956a4 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Tue, 12 Mar 2024 17:25:38 +0800 Subject: [PATCH 3/6] chore: rename some metrics name Signed-off-by: iGxnon --- crates/curp/src/rpc/metrics.rs | 4 ++-- doc/metrics.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/curp/src/rpc/metrics.rs b/crates/curp/src/rpc/metrics.rs index 7e7281070..66ff6c29b 100644 --- a/crates/curp/src/rpc/metrics.rs +++ b/crates/curp/src/rpc/metrics.rs @@ -4,11 +4,11 @@ use utils::define_metrics; define_metrics! { "curp_p2p", peer_sent_bytes_total: Counter = meter() - .u64_counter("peer_sent_bytes_total") + .u64_counter("peer_sent_bytes") .with_description("The total number of bytes send to peers.") .init(), peer_sent_failures_total: Counter = meter() - .u64_counter("peer_sent_failures_total") + .u64_counter("peer_sent_failures") .with_description("The total number of send failures to peers.") .init(), peer_round_trip_time_seconds: Histogram = meter() diff --git a/doc/metrics.md b/doc/metrics.md index 42c40cb2a..933ceb4a9 100644 --- a/doc/metrics.md +++ b/doc/metrics.md @@ -71,13 +71,13 @@ The total number of fast path fallbacks into slow path when the client propose t ### Xline -1. `slow_read_indexes_total`: Counter +1. `slow_read_indexes`: Counter The total number of pending read indexes not in sync with leader's or timed out read index requests. -2. `read_indexes_failed_total`: Counter +2. `read_indexes_failed`: Counter The total number of failed read indexes seen. -3. `lease_expired_total`: Counter +3. `lease_expired`: Counter The total number of expired leases. 4. `fd_used`: ObservableGauge @@ -103,10 +103,10 @@ The backend engine write batch engine, `batch_size` refer to the size and `sync` ### Network -1. `peer_sent_bytes_total`: Counter +1. `peer_sent_bytes`: Counter The total number of bytes sent to peers. -2. `peer_sent_failures_total`: Counter +2. `peer_sent_failures`: Counter The total number of send failures to peers. 3. `peer_round_trip_time_seconds`: Histogram From 8aa0958b73ba2a0eced63ae3d485688507a39637 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Wed, 13 Mar 2024 18:40:46 +0800 Subject: [PATCH 4/6] chore: rename sp_total to sp_cnt Signed-off-by: iGxnon --- crates/curp/src/server/metrics.rs | 8 ++++---- doc/metrics.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/curp/src/server/metrics.rs b/crates/curp/src/server/metrics.rs index 208c4e29a..b52378c40 100644 --- a/crates/curp/src/server/metrics.rs +++ b/crates/curp/src/server/metrics.rs @@ -66,7 +66,7 @@ impl Metrics { is_leader, is_learner, server_id, - sp_total, + sp_cnt, online_clients, ) = ( meter @@ -86,7 +86,7 @@ impl Metrics { .with_description("Server or member ID in hexadecimal format. 1 for 'server_id' label with current ID.") .init(), meter - .u64_observable_gauge("sp_total") + .u64_observable_gauge("sp_cnt") .with_description("The speculative pool size of this server") .init(), meter @@ -101,7 +101,7 @@ impl Metrics { is_leader.as_any(), is_learner.as_any(), server_id.as_any(), - sp_total.as_any(), + sp_cnt.as_any(), online_clients.as_any(), ], move |observer| { @@ -115,7 +115,7 @@ impl Metrics { observer.observe_u64(&server_id, id, &[]); let sp_size = curp.spec_pool().lock().len(); - observer.observe_u64(&sp_total, sp_size.numeric_cast(), &[]); + observer.observe_u64(&sp_cnt, sp_size.numeric_cast(), &[]); let client_ids = curp.lease_manager().read().expiry_queue.len(); observer.observe_u64(&online_clients, client_ids.numeric_cast(), &[]); diff --git a/doc/metrics.md b/doc/metrics.md index 933ceb4a9..36ec38f26 100644 --- a/doc/metrics.md +++ b/doc/metrics.md @@ -49,7 +49,7 @@ Whether or not this member is a learner. 1 if is, 0 otherwise. 15. `server_id`: ObservableGauge Server or member ID in hexadecimal format. 1 for 'server_id' label with the current ID. -16. `sp_total`: ObservableGauge +16. `sp_cnt`: ObservableGauge The speculative pool size of this server. 17. `online_clients`: ObservableGauge From f3c0e243dae618c4b3ebf9bfa211c7f2de3c04e4 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Fri, 15 Mar 2024 21:44:21 +0800 Subject: [PATCH 5/6] fix: move observable metrics collections Signed-off-by: iGxnon --- crates/curp/src/server/curp_node.rs | 4 +-- crates/curp/src/server/metrics.rs | 46 +++++++++++++++++--------- crates/curp/src/server/raw_curp/mod.rs | 16 +++------ doc/metrics.md | 28 ++++++++-------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/crates/curp/src/server/curp_node.rs b/crates/curp/src/server/curp_node.rs index ea0f0806b..51866774f 100644 --- a/crates/curp/src/server/curp_node.rs +++ b/crates/curp/src/server/curp_node.rs @@ -265,7 +265,7 @@ impl CurpNode { &self, req_stream: impl Stream>, ) -> Result { - metrics::get().apply_snapshot_in_progress.observe(1, &[]); + metrics::get().apply_snapshot_in_progress.add(1, &[]); let start = Instant::now(); pin_mut!(req_stream); let mut snapshot = self @@ -315,7 +315,7 @@ impl CurpNode { "failed to reset the command executor by snapshot, {err}" )) })?; - metrics::get().apply_snapshot_in_progress.observe(0, &[]); + metrics::get().apply_snapshot_in_progress.add(-1, &[]); metrics::get() .snapshot_install_total_duration_seconds .record(start.elapsed().as_secs(), &[]); diff --git a/crates/curp/src/server/metrics.rs b/crates/curp/src/server/metrics.rs index b52378c40..bcc9ba658 100644 --- a/crates/curp/src/server/metrics.rs +++ b/crates/curp/src/server/metrics.rs @@ -1,8 +1,8 @@ use std::sync::Arc; -use clippy_utilities::NumericCast; +use clippy_utilities::{NumericCast, OverflowArithmetic}; use curp_external_api::{cmd::Command, role_change::RoleChange}; -use opentelemetry::metrics::{Counter, Histogram, MetricsError, ObservableGauge}; +use opentelemetry::metrics::{Counter, Histogram, MetricsError, UpDownCounter}; use utils::define_metrics; use super::raw_curp::RawCurp; @@ -25,26 +25,14 @@ define_metrics! { .u64_counter("heartbeat_send_failures") .with_description("The total number of leader heartbeat send failures (likely overloaded from slow disk).") .init(), - apply_snapshot_in_progress: ObservableGauge = meter() - .u64_observable_gauge("apply_snapshot_in_progress") + apply_snapshot_in_progress: UpDownCounter = meter() + .i64_up_down_counter("apply_snapshot_in_progress") .with_description("1 if the server is applying the incoming snapshot. 0 if none.") .init(), - proposals_committed: ObservableGauge = meter() - .u64_observable_gauge("proposals_committed") - .with_description("The total number of consensus proposals committed.") - .init(), proposals_failed: Counter = meter() .u64_counter("proposals_failed") .with_description("The total number of failed proposals seen.") .init(), - proposals_applied: ObservableGauge = meter() - .u64_observable_gauge("proposals_applied") - .with_description("The total number of consensus proposals applied.") - .init(), - proposals_pending: ObservableGauge = meter() - .u64_observable_gauge("proposals_pending") - .with_description("The current number of pending proposals to commit.") - .init(), snapshot_install_total_duration_seconds: Histogram = meter() .u64_histogram("snapshot_install_total_duration_seconds") .with_description("The total latency distributions of save called by install_snapshot.") @@ -68,6 +56,9 @@ impl Metrics { server_id, sp_cnt, online_clients, + proposals_committed, + proposals_applied, + proposals_pending, ) = ( meter .u64_observable_gauge("has_leader") @@ -93,6 +84,18 @@ impl Metrics { .u64_observable_gauge("online_clients") .with_description("The online client ids count of this server if it is the leader") .init(), + meter + .u64_observable_gauge("proposals_committed") + .with_description("The total number of consensus proposals committed.") + .init(), + meter + .u64_observable_gauge("proposals_applied") + .with_description("The total number of consensus proposals applied.") + .init(), + meter + .u64_observable_gauge("proposals_pending") + .with_description("The current number of pending proposals to commit.") + .init(), ); _ = meter.register_callback( @@ -119,6 +122,17 @@ impl Metrics { let client_ids = curp.lease_manager().read().expiry_queue.len(); observer.observe_u64(&online_clients, client_ids.numeric_cast(), &[]); + + let commit_index = curp.commit_index(); + let last_log_index = curp.last_log_index(); + + observer.observe_u64(&proposals_committed, commit_index, &[]); + observer.observe_u64(&proposals_applied, curp.last_applied(), &[]); + observer.observe_u64( + &proposals_pending, + last_log_index.overflow_sub(commit_index), + &[], + ); }, )?; diff --git a/crates/curp/src/server/raw_curp/mod.rs b/crates/curp/src/server/raw_curp/mod.rs index 86c62eace..a129a9c71 100644 --- a/crates/curp/src/server/raw_curp/mod.rs +++ b/crates/curp/src/server/raw_curp/mod.rs @@ -768,12 +768,6 @@ impl RawCurp { if last_sent_index > log_w.commit_index { log_w.commit_to(last_sent_index); debug!("{} updates commit index to {last_sent_index}", self.id()); - metrics::get() - .proposals_committed - .observe(last_sent_index, &[]); - metrics::get() - .proposals_pending - .observe(log_w.last_log_index().overflow_sub(last_sent_index), &[]); self.apply(&mut *log_w); } } @@ -1503,6 +1497,11 @@ impl RawCurp { self.log.read().last_log_index() } + /// Get last applied index + pub(super) fn last_applied(&self) -> u64 { + self.log.read().last_as + } + /// Pick a node that has the same log as the current node pub(super) fn pick_new_leader(&self) -> Option { let last_idx = self.log.read().last_log_index(); @@ -1766,7 +1765,6 @@ impl RawCurp { /// Apply new logs fn apply(&self, log: &mut Log) { for i in (log.last_as + 1)..=log.commit_index { - metrics::get().proposals_applied.observe(i, &[]); let entry = log.get(i).unwrap_or_else(|| { unreachable!( "system corrupted, apply log[{i}] when we only have {} log entries", @@ -1898,10 +1896,6 @@ impl RawCurp { // check if commit_index needs to be updated if self.can_update_commit_index_to(log_w, index, term) && index > log_w.commit_index { log_w.commit_to(index); - metrics::get().proposals_committed.observe(index, &[]); - metrics::get() - .proposals_pending - .observe(log_w.last_log_index().overflow_sub(index), &[]); debug!("{} updates commit index to {index}", self.id()); self.apply(&mut *log_w); } diff --git a/doc/metrics.md b/doc/metrics.md index 36ec38f26..958fdcacc 100644 --- a/doc/metrics.md +++ b/doc/metrics.md @@ -16,43 +16,43 @@ The total number of successful learner promotions while this member is leader. 4. `heartbeat_send_failures`: Counter The total number of leader heartbeat send failures (likely overloaded from slow disk). -5. `apply_snapshot_in_progress`: ObservableGauge -1 if the server is applying the incoming snapshot. 0 if none. +5. `apply_snapshot_in_progress`: UpDownCounter +not equals to 0 if the server is applying the incoming snapshot. 0 if none. -6. `proposals_committed`: ObservableGauge +1. `proposals_committed`: ObservableGauge The total number of consensus proposals committed. -7. `proposals_failed`: Counter +1. `proposals_failed`: Counter The total number of failed proposals seen. -8. `proposals_applied`: ObservableGauge +1. `proposals_applied`: ObservableGauge The total number of consensus proposals applied. -9. `proposals_pending`: ObservableGauge +1. `proposals_pending`: ObservableGauge The current number of pending proposals to commit. -10. `snapshot_install_total_duration_seconds`: Histogram +1. `snapshot_install_total_duration_seconds`: Histogram The total latency distributions of save called by install_snapshot. -11. `client_id_revokes`: Counter +1. `client_id_revokes`: Counter The total number of client id revokes times. -12. `has_leader`: ObservableGauge +1. `has_leader`: ObservableGauge Whether or not a leader exists. 1 is existence, 0 is not. -13. `is_leader`: ObservableGauge +1. `is_leader`: ObservableGauge Whether or not this member is a leader. 1 if is, 0 otherwise. -14. `is_learner`: ObservableGauge +1. `is_learner`: ObservableGauge Whether or not this member is a learner. 1 if is, 0 otherwise. -15. `server_id`: ObservableGauge +1. `server_id`: ObservableGauge Server or member ID in hexadecimal format. 1 for 'server_id' label with the current ID. -16. `sp_cnt`: ObservableGauge +1. `sp_cnt`: ObservableGauge The speculative pool size of this server. -17. `online_clients`: ObservableGauge +1. `online_clients`: ObservableGauge The online client IDs count of this server if it is the leader. ### CURP Client From 239b85ce238742083c192f10baa570cd475d6926 Mon Sep 17 00:00:00 2001 From: iGxnon Date: Mon, 25 Mar 2024 12:31:13 +0800 Subject: [PATCH 6/6] chore: wrong bullet number Signed-off-by: iGxnon --- doc/metrics.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/metrics.md b/doc/metrics.md index 958fdcacc..0e8134295 100644 --- a/doc/metrics.md +++ b/doc/metrics.md @@ -19,40 +19,40 @@ The total number of leader heartbeat send failures (likely overloaded from slow 5. `apply_snapshot_in_progress`: UpDownCounter not equals to 0 if the server is applying the incoming snapshot. 0 if none. -1. `proposals_committed`: ObservableGauge +6. `proposals_committed`: ObservableGauge The total number of consensus proposals committed. -1. `proposals_failed`: Counter +7. `proposals_failed`: Counter The total number of failed proposals seen. -1. `proposals_applied`: ObservableGauge +8. `proposals_applied`: ObservableGauge The total number of consensus proposals applied. -1. `proposals_pending`: ObservableGauge +9. `proposals_pending`: ObservableGauge The current number of pending proposals to commit. -1. `snapshot_install_total_duration_seconds`: Histogram +10. `snapshot_install_total_duration_seconds`: Histogram The total latency distributions of save called by install_snapshot. -1. `client_id_revokes`: Counter +11. `client_id_revokes`: Counter The total number of client id revokes times. -1. `has_leader`: ObservableGauge +12. `has_leader`: ObservableGauge Whether or not a leader exists. 1 is existence, 0 is not. -1. `is_leader`: ObservableGauge +13. `is_leader`: ObservableGauge Whether or not this member is a leader. 1 if is, 0 otherwise. -1. `is_learner`: ObservableGauge +14. `is_learner`: ObservableGauge Whether or not this member is a learner. 1 if is, 0 otherwise. -1. `server_id`: ObservableGauge +15. `server_id`: ObservableGauge Server or member ID in hexadecimal format. 1 for 'server_id' label with the current ID. -1. `sp_cnt`: ObservableGauge +16. `sp_cnt`: ObservableGauge The speculative pool size of this server. -1. `online_clients`: ObservableGauge +17. `online_clients`: ObservableGauge The online client IDs count of this server if it is the leader. ### CURP Client