This is an automated email from the ASF dual-hosted git repository.

jinrongtong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/rocketmq-test-tool.git


The following commit(s) were added to refs/heads/main by this push:
     new ce372e5  Integrate performance benchmarking tool into CI pipeline (#7)
ce372e5 is described below

commit ce372e5f3906ca1891e4918b05be14608eae608e
Author: 小陈 <[email protected]>
AuthorDate: Fri Sep 27 15:11:12 2024 +0800

    Integrate performance benchmarking tool into CI pipeline (#7)
    
    * Wrap the performance  benchmark process into an action
    
    * Merge consumer and producer scripts into a single script
    
    * Remove redundant parameters
    
    * Add logic to check if CI passes
    
    * Reduce code duplication
    
    * Improve CI pass condition logic for more accurate validation
    
    * Revert "Reduce code duplication"
    
    This reverts commit eadddf6fc7c9eff4a92d8634792dd0b18956f5d9.
    
    * Fix bug
    
    * Simplified the validation logic
    
    * Update
    
    * Move data processing scripts into the image
    
    * Add support for setting thresholds via parameters
    
    * Use the same K8s secret configuration as the main repository
---
 benchmark-runner/Dockerfile |  24 ++++
 benchmark-runner/action.yml | 104 +++++++++++++++
 benchmark-runner/entry.sh   | 300 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+)

diff --git a/benchmark-runner/Dockerfile b/benchmark-runner/Dockerfile
new file mode 100644
index 0000000..070f1fc
--- /dev/null
+++ b/benchmark-runner/Dockerfile
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Container image that runs your code
+FROM registry.cn-guangzhou.aliyuncs.com/cc-aliyun/ubuntu-python:v1.2.0
+
+RUN pip install numpy pandas matplotlib
+
+COPY entry.sh /entry.sh
+
+ENTRYPOINT ["/entry.sh"]
\ No newline at end of file
diff --git a/benchmark-runner/action.yml b/benchmark-runner/action.yml
new file mode 100644
index 0000000..2b379a4
--- /dev/null
+++ b/benchmark-runner/action.yml
@@ -0,0 +1,104 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# action.yml
+name: 'RocketMQ Benchmark Helper'
+description: ''
+inputs:
+  action: # id of input
+    description: 'action'
+    required: true
+    default: ''
+  test-version: # id of input
+    description: 'test version'
+    required: true
+    default: ''
+  ask-config:  # id of input
+    description: 'ask config'
+    required: true
+    default: ''
+  docker-repo-username: # id of input
+    description: 'docker repo username'
+    required: false
+    default: ''
+  docker-repo-password: # id of input
+    description: 'docker repo password'
+    required: false
+    default: ''
+  chart-git: # id of input
+    description: 'chart git'
+    required: false
+    default: ''
+  chart-branch: # id of input
+    description: 'chart branch'
+    required: false
+    default: ''
+  chart-path: # id of input
+    description: 'chart path'
+    required: false
+    default: './'
+  job-id: # id of input
+    description: 'job id'
+    required: true
+    default: ''
+  helm-values: # id of input
+    description: 'helm values'
+    required: true
+    default: ''
+  test-time: 
+    description: 'test time'
+    required: false
+    default: '1800' # default 30 minutes
+  min-send-tps-threshold:
+    description: 'min-send-tps-threshold'
+    required: false
+    default: '13000' # default threshold : only one broker as master in the 
cluster
+  max-rt-ms-threshold:
+    description: 'max-rt-ms-threshold'
+    required: false
+    default: '450' 
+  avg-rt-ms-threshold:
+    description: 'avg-rt-ms-threshold'
+    required: false
+    default: '5'
+  max-2c-rt-ms-threshold:
+    description: 'max-2c-rt-ms-threshold'
+    required: false
+    default: '85' 
+  avg-2c-rt-ms-threshold:
+    description: 'avg-2c-rt-ms-threshold'
+    required: false
+    default: '6' 
+runs:
+  using: 'docker'
+  image: 'Dockerfile'
+  args:
+    - ${{ inputs.action }}
+    - ${{ inputs.test-version }}
+    - ${{ inputs.ask-config }}
+    - ${{ inputs.docker-repo-username }}
+    - ${{ inputs.docker-repo-password }}
+    - ${{ inputs.chart-git }}
+    - ${{ inputs.chart-branch }}
+    - ${{ inputs.chart-path }}
+    - ${{ inputs.job-id }}
+    - ${{ inputs.helm-values }}
+    - ${{ inputs.test-time }}
+    - ${{ inputs.min-send-tps-threshold }}
+    - ${{ inputs.max-rt-ms-threshold }}
+    - ${{ inputs.avg-rt-ms-threshold }}
+    - ${{ inputs.max-2c-rt-ms-threshold }}
+    - ${{ inputs.avg-2c-rt-ms-threshold }}
diff --git a/benchmark-runner/entry.sh b/benchmark-runner/entry.sh
new file mode 100755
index 0000000..b226370
--- /dev/null
+++ b/benchmark-runner/entry.sh
@@ -0,0 +1,300 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+ACTION=$1
+VERSION=$2
+ASK_CONFIG=$3
+DOCKER_REPO_USERNAME=$4
+DOCKER_REPO_PASSWORD=$5
+CHART_GIT=$6
+CHART_BRANCH=$7
+CHART_PATH=$8
+JOB_INDEX=${9}
+HELM_VALUES=${10}
+TEST_TIME=${11}
+MIN_SEND_TPS_THRESHOLD=${12}
+MAX_RT_MS_THRESHOLD=${13}
+AVG_RT_MS_THRESHOLD=${14}
+MAX_2C_RT_MS_THRESHOLD=${15}
+AVG_2C_RT_MS_THRESHOLD=${16}
+
+export VERSION
+export CHART_GIT
+export CHART_BRANCH
+export CHART_PATH
+export REPO_NAME=`echo ${GITHUB_REPOSITORY#*/} | sed -e "s/\//-/g" | cut 
-c1-36 | tr '[A-Z]' '[a-z]'`
+export WORKFLOW_NAME=${GITHUB_WORKFLOW}
+export RUN_ID=${GITHUB_RUN_ID}
+export YAML_VALUES=`echo "${HELM_VALUES}" | sed -s 's/^/          /g'`
+
+mkdir -p ${HOME}/.kube
+kube_config=$(echo "${ASK_CONFIG}" | base64 -d)
+echo "${kube_config}" > ${HOME}/.kube/config
+export KUBECONFIG="${HOME}/.kube/config"
+
+env_uuid=${REPO_NAME}-${GITHUB_RUN_ID}-${JOB_INDEX}
+
+VELA_APP_TEMPLATE='
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: ${VELA_APP_NAME}
+  description: ${REPO_NAME}-${WORKFLOW_NAME}-${RUN_ID}@${VERSION}
+spec:
+  components:
+    - name: ${REPO_NAME}
+      type: helm
+      properties:
+        chart: ${CHART_PATH}
+        git:
+          branch: ${CHART_BRANCH}
+        repoType: git
+        retries: 3
+        secretRef: \047\047
+        url: ${CHART_GIT}
+        values:
+${YAML_VALUES}'
+
+echo -e "${VELA_APP_TEMPLATE}" > ./velaapp.yaml
+sed -i '1d' ./velaapp.yaml
+
+if [ "${ACTION}" = "deploy" ]; then
+  echo "************************************"
+  echo "*     Create env and deploy...     *"
+  echo "************************************"
+
+  echo ${VERSION}: ${env_uuid} deploy start
+
+  vela env init ${env_uuid} --namespace ${env_uuid}
+
+  export VELA_APP_NAME=${env_uuid}
+  envsubst < ./velaapp.yaml > velaapp-${REPO_NAME}.yaml
+  cat velaapp-${REPO_NAME}.yaml
+
+  vela env set ${env_uuid}
+  vela up -f "velaapp-${REPO_NAME}.yaml"
+
+  app=${env_uuid}
+
+  status=`vela status ${app} -n ${app}`
+  echo $status
+  res=`echo $status | grep "Create helm release successfully"`
+  count=$((0))
+  while [ -z "$res" ]
+  do
+      if [ $count -gt 240 ]; then
+        echo "env ${app} deploy timeout..."
+        exit 1
+      fi
+      echo "waiting for env ${app} ready..."
+      sleep 5
+      status=`vela status ${app} -n ${app}`
+      stopped=`echo $status | grep "not found"`
+      if [ ! -z "$stopped" ]; then
+          echo "env ${app} deploy stopped..."
+          exit 1
+      fi
+      res=`echo $status | grep "Create helm release successfully"`
+      count=$((count + 1))
+  done
+fi
+
+CLIENT_POD_TEMPLATE='
+apiVersion: v1
+kind: Pod
+metadata:
+  name: ${test_pod_name}
+  labels:
+    app: rocketmq-benchmark-${test_pod_name}
+spec:
+  containers:
+    - name: ${test_pod_name}
+      image: registry.cn-guangzhou.aliyuncs.com/cc-aliyun/rocketmq:latest
+      command: ["/bin/sh", "-c"]
+      env:
+        - name: NAMESRV_ADDR
+          value: ${namesrv}
+        - name: TIMESTAMP
+          value: "${timestamp}"
+      args: ["tail -f /dev/null"]
+      resources:
+        requests:
+          memory: "4Gi"
+          cpu: "4"
+        limits:
+          memory: "4Gi"
+          cpu: "4"
+      volumeMounts:
+        - name: report-volume
+          mountPath: /mnt/report
+  volumes:
+    - name: report-volume
+      emptyDir: {}
+  restartPolicy: Never
+'
+deploy_pod() {
+    local role=$1
+    local pod_name="${role}-${env_uuid}"
+    local test_cmd=$2
+    
+    echo -e "${CLIENT_POD_TEMPLATE}" > ./${role}_pod.yaml
+    sed -i '1d' ./${role}_pod.yaml
+    export test_pod_name=$pod_name
+
+    envsubst < ./${role}_pod.yaml > ${pod_name}.yaml
+    cat ${pod_name}.yaml
+    sleep 5
+
+    kubectl apply -f ${pod_name}.yaml -n ${ns} --validate=false
+    kubectl wait --for=condition=Ready pod/${pod_name} -n ${ns} --timeout=300s
+    kubectl exec -i ${pod_name} -n ${ns} -- /bin/sh -c "$test_cmd" &
+}
+
+if [ "${ACTION}" = "performance-benchmark" ]; then
+  timestamp=$(date +%Y%m%d_%H%M%S)
+  export timestamp
+  ns=${env_uuid}
+  export ns
+  export namesrv=$(kubectl get svc -n ${ns} | grep nameserver | awk '{print 
$1}'):9876
+
+  # Deploy consumer
+  consumer_cmd='sh mqadmin updatetopic -n $NAMESRV_ADDR -t 
TestTopic_$TIMESTAMP -c DefaultCluster && cd ../benchmark/ && sh consumer.sh -n 
$NAMESRV_ADDR -t TestTopic_$TIMESTAMP > /mnt/report/consumer_$TIMESTAMP.log 
2>&1'
+  deploy_pod "consumer" "$consumer_cmd"
+
+  # Deploy producer
+  producer_cmd='cd ../benchmark/ && sh producer.sh -n $NAMESRV_ADDR -t 
TestTopic_$TIMESTAMP > /mnt/report/producer_$TIMESTAMP.log 2>&1'
+  deploy_pod "producer" "$producer_cmd"
+
+  echo "Waiting for benchmark test done..."
+  sleep ${TEST_TIME}
+
+  # Stop benchmark test
+  consumer_pod_name="consumer"-${env_uuid}
+  producer_pod_name="producer"-${env_uuid}
+  kubectl exec -i ${consumer_pod_name} -n ${ns} -- /bin/sh -c "sh 
../benchmark/shutdown.sh consumer"
+  kubectl exec -i ${producer_pod_name} -n ${ns} -- /bin/sh -c "sh 
../benchmark/shutdown.sh producer"
+
+  # Collect reports
+  path=$(pwd)
+  mkdir -p ${path}/benchmark/
+
+  kubectl cp --retries=10 ${consumer_pod_name}:/mnt/report/ ${path}/benchmark/ 
-n ${ns} 
+  kubectl cp --retries=10 ${producer_pod_name}:/mnt/report/ ${path}/benchmark/ 
-n ${ns} 
+  sleep 10
+  kubectl delete pod ${consumer_pod_name} -n ${ns}
+  kubectl delete pod ${producer_pod_name} -n ${ns}
+
+  # Process data and generate charts
+  cd ${path}/benchmark/
+  cp /benchmark/log_analysis.py ./log_analysis.py
+  python3 log_analysis.py
+  rm -f log_analysis.py consumer_performance_data.csv 
producer_performance_data.csv
+  ls
+
+  consumer_benchmark="consumer_benchmark_result.csv"
+  producer_benchmark="producer_benchmark_result.csv"
+
+  # Print the benchmark result
+  echo "====================benchmark result===================="
+  echo "Consumer benchmark result: "
+  cat ${consumer_benchmark} || echo "Consumer benchmark file not found."
+  echo "===================================="
+  echo "Producer benchmark result: "
+  cat ${producer_benchmark} || echo "Producer benchmark file not found."
+  echo "========================================================"
+
+  # Benchmark threshold
+
+  MIN_CONSUME_TPS_THRESHOLD=${MIN_SEND_TPS_THRESHOLD}
+
+  get_csv_value() {
+      local file=$1
+      local metric=$2
+      local column=$3
+      awk -F',' -v metric="$metric" -v column="$column" '
+      $1 == metric {print $column; exit}
+      ' "$file"
+  }
+
+  consume_tps_min=$(get_csv_value "$consumer_benchmark" "Consume TPS" 2)
+  max_s2c_rt=$(get_csv_value "$consumer_benchmark" "MAX(S2C) RT (ms)" 2)
+  max_b2c_rt=$(get_csv_value "$consumer_benchmark" "MAX(B2C) RT (ms)" 2)
+  avg_s2c_rt=$(get_csv_value "$consumer_benchmark" "AVG(S2C) RT (ms)" 2)
+  avg_b2c_rt=$(get_csv_value "$consumer_benchmark" "AVG(B2C) RT (ms)" 2)
+
+  send_tps_min=$(get_csv_value "$producer_benchmark" "Send TPS" 2)
+  max_rt=$(get_csv_value "$producer_benchmark" "Max RT (ms)" 2)
+  avg_rt=$(get_csv_value "$producer_benchmark" "Average RT (ms)" 2)
+
+  # Validate the result
+  consumer_tps_pass=$(awk -v a="$consume_tps_min" -v 
b="$MIN_CONSUME_TPS_THRESHOLD" 'BEGIN {print (a >= b) ? "true" : "false"}')
+  consumer_latency_pass=$(awk -v max_s2c="$max_s2c_rt" -v 
max_b2c="$max_b2c_rt" -v avg_s2c="$avg_s2c_rt" -v avg_b2c="$avg_b2c_rt" \
+      -v max_s2c_thr="$MAX_2C_RT_MS_THRESHOLD" -v 
max_b2c_thr="$MAX_2C_RT_MS_THRESHOLD" -v avg_s2c_thr="$AVG_2C_RT_MS_THRESHOLD" 
-v avg_b2c_thr="$AVG_2C_RT_MS_THRESHOLD" \
+      'BEGIN {print (max_s2c <= max_s2c_thr && max_b2c <= max_b2c_thr && 
avg_s2c <= avg_s2c_thr && avg_b2c <= avg_b2c_thr) ? "true" : "false"}')
+
+  producer_tps_pass=$(awk -v a="$send_tps_min" -v b="$MIN_SEND_TPS_THRESHOLD" 
'BEGIN {print (a >= b) ? "true" : "false"}')
+  producer_latency_pass=$(awk -v max_rt="$max_rt" -v avg_rt="$avg_rt" -v 
max_rt_thr="$MAX_RT_MS_THRESHOLD" -v avg_rt_thr="$AVG_RT_MS_THRESHOLD" \
+      'BEGIN {print (max_rt <= max_rt_thr && avg_rt <= avg_rt_thr) ? "true" : 
"false"}')
+
+  # Check if CI passes
+  if [ "$consumer_tps_pass" = "true" ] && [ "$consumer_latency_pass" = "true" 
] && \
+    [ "$producer_tps_pass" = "true" ] && [ "$producer_latency_pass" = "true" 
]; then
+      echo "All benchmarks passed."
+      exit 0
+  else
+      echo "One or more benchmarks failed."
+      [ "$consumer_tps_pass" = "false" ] && echo "Consumer TPS test failed."
+      [ "$consumer_latency_pass" = "false" ] && echo "Consumer latency test 
failed."
+      [ "$producer_tps_pass" = "false" ] && echo "Producer TPS test failed."
+      [ "$producer_latency_pass" = "false" ] && echo "Producer latency test 
failed."
+      exit 1
+  fi
+  cd -
+fi
+
+if [ "${ACTION}" = "clean" ]; then
+    echo "************************************"
+    echo "*       Delete app and env...      *"
+    echo "************************************"
+
+    env=${env_uuid}
+
+    vela delete ${env} -n ${env} -y
+    all_pod_name=`kubectl get pods --no-headers -o 
custom-columns=":metadata.name" -n ${env}`
+    for pod in $all_pod_name;
+    do
+      kubectl delete pod ${pod} -n ${env}
+    done
+
+    sleep 30
+
+    kubectl proxy &
+    PID=$!
+    sleep 3
+
+    DELETE_ENV=${env}
+
+    vela env delete ${DELETE_ENV} -y
+    sleep 3
+    kubectl delete namespace ${DELETE_ENV} --wait=false
+    kubectl get ns ${DELETE_ENV} -o json | jq '.spec.finalizers=[]' > 
ns-without-finalizers.json
+    cat ns-without-finalizers.json
+    curl -X PUT http://localhost:8001/api/v1/namespaces/${DELETE_ENV}/finalize 
-H "Content-Type: application/json" --data-binary @ns-without-finalizers.json
+
+    kill $PID
+fi
\ No newline at end of file

Reply via email to