Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ site/stats/
# Loadtesting
./scaletest/terraform/.terraform
./scaletest/terraform/.terraform.lock.hcl
terraform.tfstate.*
**/*.tfvars
scaletest/terraform/secrets.tfvars
.terraform.tfstate.*
4 changes: 2 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ site/stats/
# Loadtesting
./scaletest/terraform/.terraform
./scaletest/terraform/.terraform.lock.hcl
terraform.tfstate.*
**/*.tfvars
scaletest/terraform/secrets.tfvars
.terraform.tfstate.*
# .prettierignore.include:
# Helm templates contain variables that are invalid YAML and can't be formatted
# by Prettier.
Expand Down
File renamed without changes.
28 changes: 20 additions & 8 deletions scaletest/terraform/coder_init.sh → scaletest/lib/coder_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,27 @@ fi
[[ -n ${VERBOSE:-} ]] && set -x

CODER_URL=$1
CONFIG_DIR="${PWD}/.coderv2"
DRY_RUN="${DRY_RUN:-0}"
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
# shellcheck source=scripts/lib.sh
source "${PROJECT_ROOT}/scripts/lib.sh"
CONFIG_DIR="${PROJECT_ROOT}/scaletest/.coderv2"
ARCH="$(arch)"
if [[ "$ARCH" == "x86_64" ]]; then
ARCH="amd64"
fi
PLATFORM="$(uname | tr '[:upper:]' '[:lower:]')"

mkdir -p "${CONFIG_DIR}"
if [[ -f "${CONFIG_DIR}/coder.env" ]]; then
echo "Found existing coder.env in ${CONFIG_DIR}!"
echo "Nothing to do, exiting."
exit 0
fi

maybedryrun "$DRY_RUN" mkdir -p "${CONFIG_DIR}"
echo "Fetching Coder CLI for first-time setup!"
curl -fsSLk "${CODER_URL}/bin/coder-${PLATFORM}-${ARCH}" -o "${CONFIG_DIR}/coder"
chmod +x "${CONFIG_DIR}/coder"
maybedryrun "$DRY_RUN" curl -fsSLk "${CODER_URL}/bin/coder-${PLATFORM}-${ARCH}" -o "${CONFIG_DIR}/coder"
maybedryrun "$DRY_RUN" chmod +x "${CONFIG_DIR}/coder"

set +o pipefail
RANDOM_ADMIN_PASSWORD=$(tr </dev/urandom -dc _A-Z-a-z-0-9 | head -c16)
Expand All @@ -31,21 +41,23 @@ CODER_FIRST_USER_USERNAME="coder"
CODER_FIRST_USER_PASSWORD="${RANDOM_ADMIN_PASSWORD}"
CODER_FIRST_USER_TRIAL="false"
echo "Running login command!"
"${CONFIG_DIR}/coder" login "${CODER_URL}" \
DRY_RUN="$DRY_RUN" "${PROJECT_ROOT}/scaletest/lib/coder_shim.sh" login "${CODER_URL}" \
--global-config="${CONFIG_DIR}" \
--first-user-username="${CODER_FIRST_USER_USERNAME}" \
--first-user-email="${CODER_FIRST_USER_EMAIL}" \
--first-user-password="${CODER_FIRST_USER_PASSWORD}" \
--first-user-trial=false

echo "Writing credentials to ${CONFIG_DIR}/coder.env"
cat <<EOF >"${CONFIG_DIR}/coder.env"
maybedryrun "$DRY_RUN" cat <<EOF >"${CONFIG_DIR}/coder.env"
CODER_FIRST_USER_EMAIL=admin@coder.com
CODER_FIRST_USER_USERNAME=coder
CODER_FIRST_USER_PASSWORD="${RANDOM_ADMIN_PASSWORD}"
CODER_FIRST_USER_TRIAL="${CODER_FIRST_USER_TRIAL}"
EOF

echo "Importing kubernetes template"
"${CONFIG_DIR}/coder" templates create --global-config="${CONFIG_DIR}" \
--directory "${CONFIG_DIR}/templates/kubernetes" --yes kubernetes
DRY_RUN="$DRY_RUN" "$PROJECT_ROOT/scaletest/lib/coder_shim.sh" templates create \
--global-config="${CONFIG_DIR}" \
--directory "${CONFIG_DIR}/templates/kubernetes" \
--yes kubernetes
11 changes: 11 additions & 0 deletions scaletest/lib/coder_shim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

# This is a shim for easily executing Coder commands against a loadtest cluster
# without having to overwrite your own session/URL
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
# shellcheck source=scripts/lib.sh
source "${PROJECT_ROOT}/scripts/lib.sh"
CONFIG_DIR="${PROJECT_ROOT}/scaletest/.coderv2"
CODER_BIN="${CONFIG_DIR}/coder"
DRY_RUN="${DRY_RUN:-0}"
maybedryrun "$DRY_RUN" exec "${CODER_BIN}" --global-config "${CONFIG_DIR}" "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ fi
[[ -n ${VERBOSE:-} ]] && set -x

LOADTEST_NAME="$1"
CODER_TOKEN=$(./coder_shim.sh tokens create)
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
CODER_TOKEN=$("${PROJECT_ROOT}/scaletest/lib/coder_shim.sh" tokens create)
CODER_URL="http://coder.coder-${LOADTEST_NAME}.svc.cluster.local"
export KUBECONFIG="${PWD}/.coderv2/${LOADTEST_NAME}-cluster.kubeconfig"
export KUBECONFIG="${PROJECT_ROOT}/scaletest/.coderv2/${LOADTEST_NAME}-cluster.kubeconfig"

# Clean up any pre-existing pods
kubectl -n "coder-${LOADTEST_NAME}" delete pod coder-scaletest-workspace-traffic --force || true

cat <<EOF | kubectl apply -f -
apiVersion: v1
Expand All @@ -37,7 +41,7 @@ spec:
- command:
- sh
- -c
- "curl -fsSL $CODER_URL/bin/coder-linux-amd64 -o /tmp/coder && chmod +x /tmp/coder && /tmp/coder --url=$CODER_URL --token=$CODER_TOKEN scaletest workspace-traffic"
- "curl -fsSL $CODER_URL/bin/coder-linux-amd64 -o /tmp/coder && chmod +x /tmp/coder && /tmp/coder --verbose --url=$CODER_URL --token=$CODER_TOKEN scaletest workspace-traffic --concurrency=0 --bytes-per-tick=4096 --tick-interval=100ms"
env:
- name: CODER_URL
value: $CODER_URL
Expand Down
182 changes: 182 additions & 0 deletions scaletest/scaletest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env bash

[[ -n ${VERBOSE:-} ]] && set -x
set -euo pipefail

PROJECT_ROOT="$(git rev-parse --show-toplevel)"
# shellcheck source=scripts/lib.sh
source "${PROJECT_ROOT}/scripts/lib.sh"

DRY_RUN="${DRY_RUN:-0}"
SCALETEST_NAME="${SCALETEST_NAME:-}"
SCALETEST_NUM_WORKSPACES="${SCALETEST_NUM_WORKSPACES:-}"
SCALETEST_SCENARIO="${SCALETEST_SCENARIO:-}"
SCALETEST_PROJECT="${SCALETEST_PROJECT:-}"
SCALETEST_PROMETHEUS_REMOTE_WRITE_USER="${SCALETEST_PROMETHEUS_REMOTE_WRITE_USER:-}"
SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD="${SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD:-}"
SCALETEST_SKIP_CLEANUP="${SCALETEST_SKIP_CLEANUP:-}"

script_name=$(basename "$0")
args="$(getopt -o "" -l dry-run,help,name:,num-workspaces:,project:,scenario:,skip-cleanup -- "$@")"
eval set -- "$args"
while true; do
case "$1" in
--dry-run)
DRY_RUN=1
shift
;;
--help)
echo "Usage: $script_name --name <name> --project <project> [--num-workspaces <num-workspaces>] [--scenario <scenario>] [--dry-run]"
exit 1
;;
--name)
SCALETEST_NAME="$2"
shift 2
;;
--num-workspaces)
SCALETEST_NUM_WORKSPACES="$2"
shift 2
;;
--project)
SCALETEST_PROJECT="$2"
shift 2
;;
--scenario)
SCALETEST_SCENARIO="$2"
shift 2
;;
--skip-cleanup)
SCALETEST_SKIP_CLEANUP=1
shift
;;
--)
shift
break
;;
*)
error "Unrecognized option: $1"
;;
esac
done

dependencies gcloud kubectl terraform

if [[ -z "${SCALETEST_NAME}" ]]; then
echo "Must specify --name"
exit 1
fi

if [[ -z "${SCALETEST_PROJECT}" ]]; then
echo "Must specify --project"
exit 1
fi

if [[ -z "${SCALETEST_NUM_WORKSPACES}" ]]; then
echo "Must specify --num-workspaces"
exit 1
fi

if [[ -z "${SCALETEST_SCENARIO}" ]]; then
echo "Must specify --scenario"
exit 1
fi

if [[ -z "${SCALETEST_PROMETHEUS_REMOTE_WRITE_USER}" ]] || [[ -z "${SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD}" ]]; then
echo "SCALETEST_PROMETHEUS_REMOTE_WRITE_USER or SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD not specified."
echo "No prometheus metrics will be collected!"
read -p "Continue (y/N)? " choice
case "$choice" in
y|Y|yes|YES ) ;;
* ) exit 1;;
esac
fi

SCALETEST_SCENARIO_VARS="${PROJECT_ROOT}/scaletest/terraform/scenario-${SCALETEST_SCENARIO}.tfvars"
if [[ ! -f "${SCALETEST_SCENARIO_VARS}" ]]; then
echo "Scenario ${SCALETEST_SCENARIO_VARS} not found."
echo "Please create it or choose another scenario:"
find "${PROJECT_ROOT}/scaletest/terraform" -type f -name 'scenario-*.tfvars'
exit 1
fi

if [[ "${SCALETEST_SKIP_CLEANUP}" == "true" ]]; then
log "WARNING: you told me to not clean up after myself, so this is now your job!"
fi

CONFIG_DIR="${PROJECT_ROOT}/scaletest/.coderv2"
SCALETEST_SCENARIO_VARS="${PROJECT_ROOT}/scaletest/terraform/scenario-${SCALETEST_SCENARIO}.tfvars"
SCALETEST_SECRETS="${PROJECT_ROOT}/scaletest/terraform/secrets.tfvars"
SCALETEST_SECRETS_TEMPLATE="${PROJECT_ROOT}/scaletest/terraform/secrets.tfvars.tpl"

log "Writing scaletest secrets to file."
SCALETEST_NAME="${SCALETEST_NAME}" \
SCALETEST_PROJECT="${SCALETEST_PROJECT}" \
SCALETEST_PROMETHEUS_REMOTE_WRITE_USER="${SCALETEST_PROMETHEUS_REMOTE_WRITE_USER}" \
SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD="${SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD}" \
envsubst <"${SCALETEST_SECRETS_TEMPLATE}" >"${SCALETEST_SECRETS}"

pushd "${PROJECT_ROOT}/scaletest/terraform"

echo "Initializing terraform."
maybedryrun "$DRY_RUN" terraform init

echo "Setting up infrastructure."
maybedryrun "$DRY_RUN" terraform apply --var-file="${SCALETEST_SCENARIO_VARS}" --var-file="${SCALETEST_SECRETS}" --auto-approve

if [[ "${DRY_RUN}" != 1 ]]; then
SCALETEST_CODER_URL=$(<"${CONFIG_DIR}/url")
else
SCALETEST_CODER_URL="http://coder.dryrun.local:3000"
fi
KUBECONFIG="${PWD}/.coderv2/${SCALETEST_NAME}-cluster.kubeconfig"
echo "Waiting for Coder deployment at ${SCALETEST_CODER_URL} to become ready"
maybedryrun "$DRY_RUN" kubectl --kubeconfig="${KUBECONFIG}" -n "coder-${SCALETEST_NAME}" rollout status deployment/coder

echo "Initializing Coder deployment."
DRY_RUN="$DRY_RUN" "${PROJECT_ROOT}/scaletest/lib/coder_init.sh" "${SCALETEST_CODER_URL}"

echo "Creating ${SCALETEST_NUM_WORKSPACES} workspaces."
DRY_RUN="$DRY_RUN" "${PROJECT_ROOT}/scaletest/lib/coder_shim.sh" scaletest create-workspaces \
--count "${SCALETEST_NUM_WORKSPACES}" \
--template=kubernetes \
--concurrency 10 \
--no-cleanup

echo "Sleeping 10 minutes to establish a baseline measurement."
maybedryrun "$DRY_RUN" sleep 600

echo "Sending traffic to workspaces"
maybedryrun "$DRY_RUN" "${PROJECT_ROOT}/scaletest/lib/coder_workspacetraffic.sh" "${SCALETEST_NAME}"
maybedryrun "$DRY_RUN" kubectl --kubeconfig="${KUBECONFIG}" -n "coder-${SCALETEST_NAME}" wait pods coder-scaletest-workspace-traffic --for condition=Ready
maybedryrun "$DRY_RUN" kubectl --kubeconfig="${KUBECONFIG}" -n "coder-${SCALETEST_NAME}" logs -f pod/coder-scaletest-workspace-traffic

echo "Starting pprof"
maybedryrun "$DRY_RUN" kubectl -n "coder-${SCALETEST_NAME}" port-forward deployment/coder 6061:6060 &
pfpid=$!
maybedryrun "$DRY_RUN" trap 'kill $pfpid' EXIT

echo "Waiting for pprof endpoint to become available"
pprof_attempt_counter=0
while ! maybedryrun "$DRY_RUN" timeout 1 bash -c "echo > /dev/tcp/localhost/6061"; do
if [[ $pprof_attempt_counter -eq 10 ]]; then
echo
echo "pprof failed to become ready in time!"
exit 1
fi
maybedryrun "$DRY_RUN" sleep 3
done

echo "Taking pprof snapshots"
maybedryrun "$DRY_RUN" curl --silent --fail --output "${SCALETEST_NAME}-heap.pprof.gz" http://localhost:6061/debug/pprof/heap
maybedryrun "$DRY_RUN" curl --silent --fail --output "${SCALETEST_NAME}-goroutine.pprof.gz" http://localhost:6061/debug/pprof/goroutine
maybedryrun "$DRY_RUN" kill $pfpid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Could remove trap handler here to avoid error on exit due to missing pid:

Suggested change
maybedryrun "$DRY_RUN" kill $pfpid
maybedryrun "$DRY_RUN" kill $pfpid
maybedryrun "$DRY_RUN" trap - EXIT


if [[ "${SCALETEST_SKIP_CLEANUP}" == "true" ]]; then
echo "Leaving resources up for you to inspect."
echo "Please don't forget to clean up afterwards:"
echo "cd terraform && terraform destroy --var-file=${SCALETEST_SCENARIO_VARS} --var-file=${SCALETEST_SECRETS} --auto-approve"
exit 0
fi

echo "Cleaning up"
maybedryrun "$DRY_RUN" terraform destroy --var-file="${SCALETEST_SCENARIO_VARS}" --var-file="${SCALETEST_SECRETS}" --auto-approve
9 changes: 8 additions & 1 deletion scaletest/terraform/coder.tf
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ coder:
secretKeyRef:
name: "${kubernetes_secret.coder-db.metadata.0.name}"
key: url
- name: "CODER_PPROF_ENABLE"
value: "true"
- name: "CODER_PROMETHEUS_ENABLE"
value: "true"
- name: "CODER_VERBOSE"
Expand Down Expand Up @@ -129,7 +131,7 @@ EOF
}

resource "local_file" "kubernetes_template" {
filename = "${path.module}/.coderv2/templates/kubernetes/main.tf"
filename = "${path.module}/../.coderv2/templates/kubernetes/main.tf"
content = <<EOF
terraform {
required_providers {
Expand Down Expand Up @@ -216,6 +218,11 @@ resource "local_file" "kubernetes_template" {
EOF
}

resource "local_file" "output_vars" {
filename = "${path.module}/../.coderv2/url"
content = local.coder_url
}

output "coder_url" {
description = "URL of the Coder deployment"
value = local.coder_url
Expand Down
8 changes: 0 additions & 8 deletions scaletest/terraform/coder_shim.sh

This file was deleted.

4 changes: 2 additions & 2 deletions scaletest/terraform/prometheus.tf
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ prometheus:
# after creating a cluster, and we want this to be brought up
# with a single command.
resource "local_file" "coder-monitoring-manifest" {
filename = "${path.module}/.coderv2/coder-monitoring.yaml"
filename = "${path.module}/../.coderv2/coder-monitoring.yaml"
depends_on = [helm_release.prometheus-chart]
content = <<EOF
apiVersion: monitoring.coreos.com/v1
Expand All @@ -122,7 +122,7 @@ spec:

resource "null_resource" "coder-monitoring-manifest_apply" {
provisioner "local-exec" {
working_dir = "${abspath(path.module)}/.coderv2"
working_dir = "${abspath(path.module)}/../.coderv2"
command = <<EOF
KUBECONFIG=${var.name}-cluster.kubeconfig gcloud container clusters get-credentials ${google_container_cluster.primary.name} --project=${var.project_id} --zone=${var.zone} && \
KUBECONFIG=${var.name}-cluster.kubeconfig kubectl apply -f ${abspath(local_file.coder-monitoring-manifest.filename)}
Expand Down
4 changes: 4 additions & 0 deletions scaletest/terraform/scenario-large.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nodepool_machine_type_coder = "t2d-standard-8"
nodepool_machine_type_workspaces = "t2d-standard-8"
coder_cpu = "7" # Leaving 1 CPU for system workloads
coder_mem = "28Gi" # Leaving 4GB for system workloads
5 changes: 5 additions & 0 deletions scaletest/terraform/scenario-large2x.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nodepool_machine_type_coder = "t2d-standard-8"
nodepool_machine_type_workspaces = "t2d-standard-8"
nodepool_size_workspaces = 2
coder_cpu = "7" # Leaving 1 CPU for system workloads
coder_mem = "28Gi" # Leaving 4 GB for system workloads
4 changes: 4 additions & 0 deletions scaletest/terraform/scenario-medium.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nodepool_machine_type_coder = "t2d-standard-4"
nodepool_machine_type_workspaces = "t2d-standard-4"
coder_cpu = "3000m" # Leaving 1 CPU for system workloads
coder_mem = "12Gi" # Leaving 4 GB for system workloads
5 changes: 5 additions & 0 deletions scaletest/terraform/scenario-medium2x.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nodepool_machine_type_coder = "t2d-standard-8"
nodepool_machine_type_workspaces = "t2d-standard-8"
nodepool_size_workspaces = 2
coder_cpu = "3000m" # Leaving 1 CPU for system workloads
coder_mem = "12Gi" # Leaving 4 GB for system workloads
4 changes: 4 additions & 0 deletions scaletest/terraform/scenario-small.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nodepool_machine_type_coder = "t2d-standard-2"
nodepool_machine_type_workspaces = "t2d-standard-2"
coder_cpu = "1000m" # Leaving 1 CPU for system workloads
coder_mem = "4Gi" # Leaving 4GB for system workloads
5 changes: 5 additions & 0 deletions scaletest/terraform/scenario-small2x.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nodepool_machine_type_coder = "t2d-standard-2"
nodepool_machine_type_workspaces = "t2d-standard-2"
nodepool_size_workspaces = 2
coder_cpu = "1000m" # Leaving 1 CPU for system workloads
coder_mem = "4Gi" # Leaving 4 GB for system workloads
4 changes: 4 additions & 0 deletions scaletest/terraform/secrets.tfvars.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = "${SCALETEST_NAME}"
project_id = "${SCALETEST_PROJECT}"
prometheus_remote_write_user = "${SCALETEST_PROMETHEUS_REMOTE_WRITE_USER}"
prometheus_remote_write_password = "${SCALETEST_PROMETHEUS_REMOTE_WRITE_PASSWORD}"
Loading